Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Internal -> Buffer; invert MonadBuffer module dependency #51

Closed
wants to merge 11 commits into from
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,45 @@ Notable changes to this project are documented in this file. The format is based
## [Unreleased]

Breaking changes:
- Invert module dependency of `Node.Buffer.Class` (#51 by @JordanMartinez)

The `MonadBuffer` type class was seemingly used to expose an API
for both `Buffer` and `STBuffer` without duplicating FFI.
Members of the class (e.g. `size`, `toString`, etc.) had type inference issues
due to this class-based approach.

Now, both `Buffer` and `STBuffer` expose the same API but with their
type signatures hard-coded to `Effect` and `ST`, respectively, thereby
improving type inference. `MonadBuffer` instances for both types were
moved into the `Node/Buffer/Class.purs` file.

One can migrate their code by either removing the usage of `MonadClass` in their code
if it was only used to get the needed API for a given type (e.g. `Buffer`)
or update their module imports from `Node.Buffer`/`Node.Buffer.ST` to `Node.Buffer.Class`.
- `Encoding` type - dropped encodings that are only aliases (#51 by @JordanMartinez)

- `Binary`, an alias for `Latin1` was dropped.
- `UCS2`, an alias for `UTF16LE` was dropped.
- `Encoding` type - added `Base64Url` encoding (supported in Node 18) (#51 by @JordanMartinez)

New features:
- Added the following APIs

- `Buffer.alloc`, `Buffer.allocUnsafe`, `Buffer.allocUnsafeSlow`
- `Buffer.poolSize`, `Buffer.setPoolSize`
- `buffer.swap16`, `buffer.swap32`, `buffer.swap64`
- `buffer.compare`: https://nodejs.org/docs/latest-v18.x/api/buffer.html#bufcomparetarget-targetstart-targetend-sourcestart-sourceend
- `buffer.toString(encoding, start, end)`: https://nodejs.org/docs/latest-v18.x/api/buffer.html#buftostringencoding-start-end
- constants:
- `INSPECT_MAX_BYTES`: https://nodejs.org/docs/latest-v18.x/api/buffer.html#bufferinspect_max_bytes
- `MAX_LENGTH`: https://nodejs.org/docs/latest-v18.x/api/buffer.html#bufferconstantsmax_length
- `MAX_STRING_LENGTH`: https://nodejs.org/docs/latest-v18.x/api/buffer.html#bufferconstantsmax_string_length

Bugfixes:

Other improvements:
- Update all FFI to use uncurried functions (#51 by @JordanMartinez)
- Removal of the `Internal.purs` file (#51 by @JordanMartinez)

## [v8.0.0](https://github.com/purescript-node/purescript-node-buffer/releases/tag/v8.0.0) - 2022-04-27

Expand Down
3 changes: 2 additions & 1 deletion bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"purescript-effect": "^4.0.0",
"purescript-maybe": "^6.0.0",
"purescript-st": "^6.0.0",
"purescript-unsafe-coerce": "^6.0.0"
"purescript-unsafe-coerce": "^6.0.0",
"purescript-nullable": "^6.0.0"
},
"devDependencies": {
"purescript-assert": "^6.0.0",
Expand Down
33 changes: 33 additions & 0 deletions src/Node/Buffer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Buffer } from "node:buffer";

export const allocUnsafeImpl = (size) => Buffer.allocUnsafe(size);
export const allocUnsafeSlowImpl = (size) => Buffer.allocUnsafeSlow(size);

export const freezeImpl= (a) => Buffer.from(a);

export const thawImpl = (a) => Buffer.from(a);

export const writeInternal = (ty, value, offset, buf) =>
buf["write" + ty](value, offset);

export const writeStringInternal = (encoding, offset, length, value, buff) =>
buff.write(value, offset, length, encoding);

export const setAtOffsetImpl = (value, offset, buff) => {
buff[offset] = value;
};

export const copyImpl = (srcStart, srcEnd, src, targStart, targ) =>
src.copy(targ, targStart, srcStart, srcEnd);

export const fillImpl = (octet, start, end, buf) => buf.fill(octet, start, end);

export const poolSize = () => Buffer.poolSize;

export const setPoolSizeImpl = (size) => {
Buffer.poolSize = size;
};

export const swap16Impl = (buf) => buf.swap16();
export const swap32Impl = (buf) => buf.swap32();
export const swap64Impl = (buf) => buf.swap64();
213 changes: 185 additions & 28 deletions src/Node/Buffer.purs
Original file line number Diff line number Diff line change
@@ -1,40 +1,197 @@
-- | Mutable buffers and associated operations.
module Node.Buffer
( Buffer
, create
, alloc
, allocUnsafe
, allocUnsafeSlow
, compareParts
, freeze
, unsafeFreeze
, thaw
, unsafeThaw
, fromArray
, fromString
, fromArrayBuffer
, toArrayBuffer
, read
, readString
, toString
, toString'
, write
, writeString
, toArray
, getAtOffset
, setAtOffset
, slice
, size
, concat
, concat'
, copy
, fill
, poolSize
, setPoolSize
, swap16
, swap32
, swap64
, module TypesExports
, module Class
) where

import Prelude

import Data.ArrayBuffer.Types (ArrayBuffer)
import Data.Maybe (Maybe)
import Effect (Effect)
import Node.Buffer.Class (class MutableBuffer)
import Node.Buffer.Class (class MutableBuffer, concat, concat', copy, create, fill, freeze, fromArray, fromArrayBuffer, fromString, getAtOffset, read, readString, setAtOffset, size, slice, thaw, toArray, toArrayBuffer, toString, unsafeFreeze, unsafeThaw, write, writeString) as Class
import Node.Buffer.Internal as Internal
import Effect.Uncurried (EffectFn1, EffectFn3, EffectFn4, EffectFn5, runEffectFn1, runEffectFn3, runEffectFn4, runEffectFn5)
import Node.Buffer.Immutable (ImmutableBuffer)
import Node.Buffer.Immutable as Immutable
import Node.Buffer.Types (BufferValueType(..), Octet, Offset) as TypesExports
import Node.Buffer.Types (BufferValueType, Octet, Offset)
import Node.Encoding (Encoding, encodingToNode)
import Unsafe.Coerce (unsafeCoerce)

-- | A reference to a mutable buffer for use with `Effect`
foreign import data Buffer :: Type

instance mutableBufferEffect :: MutableBuffer Buffer Effect where
create = Internal.create
freeze = Internal.copyAll
unsafeFreeze = Internal.unsafeFreeze
thaw = Internal.copyAll
unsafeThaw = Internal.unsafeThaw
fromArray = Internal.fromArray
fromString = Internal.fromString
fromArrayBuffer = Internal.fromArrayBuffer
toArrayBuffer = Internal.toArrayBuffer
read = Internal.read
readString = Internal.readString
toString = Internal.toString
write = Internal.write
writeString = Internal.writeString
toArray = Internal.toArray
getAtOffset = Internal.getAtOffset
setAtOffset = Internal.setAtOffset
slice = Internal.slice
size = Internal.size
concat = Internal.concat
concat' = Internal.concat'
copy = Internal.copy
fill = Internal.fill
unsafeFreeze :: Buffer -> Effect ImmutableBuffer
unsafeFreeze = pure <<< unsafeCoerce

unsafeThaw :: ImmutableBuffer -> Effect Buffer
unsafeThaw = pure <<< unsafeCoerce

usingFromImmutable :: forall a. (ImmutableBuffer -> a) -> Buffer -> Effect a
usingFromImmutable f buf = f <$> unsafeFreeze buf

usingToImmutable :: forall a. (a -> ImmutableBuffer) -> a -> Effect Buffer
usingToImmutable f x = unsafeThaw $ f x

-- | Alias to `alloc`.
create :: Int -> Effect Buffer
create = alloc

alloc :: Int -> Effect Buffer
alloc = usingToImmutable Immutable.alloc

-- | Creates a new buffer of the specified size. Unsafe because it reuses memory from a pool
-- | and may contain sensitive data. See the Node docs.
allocUnsafe :: Int -> Effect Buffer
allocUnsafe s = runEffectFn1 allocUnsafeImpl s

foreign import allocUnsafeImpl :: EffectFn1 (Int) (Buffer)

-- | Creates a new buffer of the specified size. Unsafe because it reuses memory from a pool
-- | and may contain sensitive data. See the Node docs.
allocUnsafeSlow :: Int -> Effect Buffer
allocUnsafeSlow s = runEffectFn1 allocUnsafeSlowImpl s

foreign import allocUnsafeSlowImpl :: EffectFn1 (Int) (Buffer)

freeze :: Buffer -> Effect ImmutableBuffer
freeze = runEffectFn1 freezeImpl

thaw :: ImmutableBuffer -> Effect Buffer
thaw = runEffectFn1 thawImpl

foreign import thawImpl :: EffectFn1 ImmutableBuffer Buffer

foreign import freezeImpl :: EffectFn1 Buffer ImmutableBuffer

fromArray :: Array Octet -> Effect Buffer
fromArray = usingToImmutable Immutable.fromArray

fromString :: String -> Encoding -> Effect Buffer
fromString s = usingToImmutable $ Immutable.fromString s

fromArrayBuffer :: ArrayBuffer -> Effect Buffer
fromArrayBuffer = usingToImmutable Immutable.fromArrayBuffer

toArrayBuffer :: Buffer -> Effect ArrayBuffer
toArrayBuffer = usingFromImmutable Immutable.toArrayBuffer

compareParts :: Buffer -> Buffer -> Int -> Int -> Int -> Int -> Effect Ordering
compareParts src target targetSrc targetEnd srcStart srcEnd = do
src' <- unsafeFreeze src
target' <- unsafeFreeze target
Immutable.compareParts src' target' targetSrc targetEnd srcStart srcEnd

read :: BufferValueType -> Offset -> Buffer -> Effect Number
read t o = usingFromImmutable $ Immutable.read t o

readString :: Encoding -> Offset -> Offset -> Buffer -> Effect String
readString m o o' = usingFromImmutable $ Immutable.readString m o o'

toString :: Encoding -> Buffer -> Effect String
toString m = usingFromImmutable $ Immutable.toString m

toString' :: Encoding -> Offset -> Offset -> Buffer -> Effect String
toString' enc start end = usingFromImmutable $ Immutable.toString' enc start end

write :: BufferValueType -> Number -> Offset -> Buffer -> Effect Unit
write ty num off buf = runEffectFn4 writeInternal (show ty) num off buf

foreign import writeInternal :: EffectFn4 String Number Offset Buffer Unit

writeString :: Encoding -> Offset -> Int -> String -> Buffer -> Effect Int
writeString enc off i s b =
runEffectFn5 writeStringInternal (encodingToNode enc) off i s b

foreign import writeStringInternal
:: EffectFn5 String Offset Int String Buffer Int

toArray :: Buffer -> Effect (Array Octet)
toArray = usingFromImmutable Immutable.toArray

getAtOffset :: Offset -> Buffer -> Effect (Maybe Octet)
getAtOffset o = usingFromImmutable $ Immutable.getAtOffset o

setAtOffset :: Octet -> Offset -> Buffer -> Effect Unit
setAtOffset s e b = runEffectFn3 setAtOffsetImpl s e b

foreign import setAtOffsetImpl :: EffectFn3 Octet Offset Buffer Unit

slice :: Offset -> Offset -> Buffer -> Buffer
slice = unsafeCoerce Immutable.slice

size :: Buffer -> Effect Int
size = usingFromImmutable Immutable.size

concat :: Array Buffer -> Effect Buffer
concat arrs = unsafeCoerce \_ -> Immutable.concat (unsafeCoerce arrs)

concat' :: Array Buffer -> Int -> Effect Buffer
concat' arrs n = unsafeCoerce \_ -> Immutable.concat' (unsafeCoerce arrs) n

copy :: Offset -> Offset -> Buffer -> Offset -> Buffer -> Effect Int
copy srcStart srcEnd src targStart targ =
runEffectFn5 copyImpl srcStart srcEnd src targStart targ

foreign import copyImpl :: EffectFn5 Offset Offset Buffer Offset Buffer Int

fill :: Octet -> Offset -> Offset -> Buffer -> Effect Unit
fill octet start end buf =
runEffectFn4 fillImpl octet start end buf

foreign import fillImpl :: EffectFn4 Octet Offset Offset Buffer Unit

-- | The size (in bytes) of pre-allocated internal Buffer instances used for pooling. This value may be modified.
foreign import poolSize :: Effect (Int)

setPoolSize :: Int -> Effect Unit
setPoolSize sizeInBytes = runEffectFn1 setPoolSizeImpl sizeInBytes

foreign import setPoolSizeImpl :: EffectFn1 (Int) (Unit)

swap16 :: Buffer -> Effect Buffer
swap16 b = runEffectFn1 swap16Impl b

foreign import swap16Impl :: EffectFn1 (Buffer) (Buffer)

swap32 :: Buffer -> Effect Buffer
swap32 b = runEffectFn1 swap32Impl b

foreign import swap32Impl :: EffectFn1 (Buffer) (Buffer)

swap64 :: Buffer -> Effect Buffer
swap64 b = runEffectFn1 swap64Impl b

foreign import swap64Impl :: EffectFn1 (Buffer) (Buffer)
54 changes: 54 additions & 0 deletions src/Node/Buffer/Class.purs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,13 @@ module Node.Buffer.Class

import Prelude

import Control.Monad.ST (ST)
import Data.ArrayBuffer.Types (ArrayBuffer)
import Data.Maybe (Maybe)
import Effect (Effect)
import Node.Buffer as Buffer
import Node.Buffer.Immutable (ImmutableBuffer)
import Node.Buffer.ST as ST
import Node.Buffer.Types (BufferValueType, Octet, Offset)
import Node.Encoding (Encoding)

Expand Down Expand Up @@ -113,3 +117,53 @@ class Monad m <= MutableBuffer buf m | buf -> m where

-- | Fills a range in a buffer with the specified octet.
fill :: Octet -> Offset -> Offset -> buf -> m Unit

instance mutableBufferEffect :: MutableBuffer Buffer.Buffer Effect where
create = Buffer.create
freeze = Buffer.freeze
unsafeFreeze = Buffer.unsafeFreeze
thaw = Buffer.thaw
unsafeThaw = Buffer.unsafeThaw
fromArray = Buffer.fromArray
fromString = Buffer.fromString
fromArrayBuffer = Buffer.fromArrayBuffer
toArrayBuffer = Buffer.toArrayBuffer
read = Buffer.read
readString = Buffer.readString
toString = Buffer.toString
write = Buffer.write
writeString = Buffer.writeString
toArray = Buffer.toArray
getAtOffset = Buffer.getAtOffset
setAtOffset = Buffer.setAtOffset
slice = Buffer.slice
size = Buffer.size
concat = Buffer.concat
concat' = Buffer.concat'
copy = Buffer.copy
fill = Buffer.fill

instance mutableBufferST :: MutableBuffer (ST.STBuffer h) (ST h) where
create = ST.create
freeze = ST.freeze
unsafeFreeze = ST.unsafeFreeze
thaw = ST.thaw
unsafeThaw = ST.unsafeThaw
fromArray = ST.fromArray
fromString = ST.fromString
fromArrayBuffer = ST.fromArrayBuffer
toArrayBuffer = ST.toArrayBuffer
read = ST.read
readString = ST.readString
toString = ST.toString
write = ST.write
writeString = ST.writeString
toArray = ST.toArray
getAtOffset = ST.getAtOffset
setAtOffset = ST.setAtOffset
slice = ST.slice
size = ST.size
concat = ST.concat
concat' = ST.concat'
copy = ST.copy
fill = ST.fill
5 changes: 5 additions & 0 deletions src/Node/Buffer/Constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import buffer from "node:buffer";

export const inspectMaxBytes = () => buffer.INSPECT_MAX_LENGTH;
export const maxLength = buffer.constants.MAX_LENGTH;
export const maxStringLength = buffer.constants.MAX_STRING_LENGTH;
9 changes: 9 additions & 0 deletions src/Node/Buffer/Constants.purs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module Node.Buffer.Constants where

import Effect (Effect)

foreign import inspectMaxBytes :: Effect Int

foreign import maxLength :: Int

foreign import maxStringLength :: Int
Loading