fix: init

This commit is contained in:
orion 2024-05-09 17:21:15 -05:00
commit e0cf34f420
Signed by: orion
GPG Key ID: 6D4165AE4C928719
17 changed files with 1613 additions and 0 deletions

12
.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
bower_components/
node_modules/
.pulp-cache/
output/
output-es/
generated-docs/
.psc-package/
.psc*
.purs*
.psa*
.spago
.tmp/

1
.tool-versions Normal file
View File

@ -0,0 +1 @@
purescript 0.15.15

81
README.md Normal file
View File

@ -0,0 +1,81 @@
# purescript-csv-stream
Type-safe bindings for the streaming API of `csv-parse` and `csv-stringify`.
## Installing
```bash
spago install csv-stream
{bun|yarn|npm|pnpm} install csv-parse csv-stringify
```
## Examples
### Stream
```purescript
module Main where
import Prelude
import Effect (Effect)
import Effect.Class (liftEffect)
import Effect.Aff (launchAff_)
import Node.Stream (pipe)
import Node.Stream as Stream
import Node.Stream.CSV.Stringify as CSV.Stringify
import Node.Stream.CSV.Parse as CSV.Parse
type MyCSVType1 = {a :: Int, b :: Int, bar :: String, baz :: Boolean}
type MyCSVType2 = {ab :: Int, bar :: String, baz :: Boolean}
atob :: MyCSVType1 -> MyCSVType2
atob {a, b, bar, baz} = {ab: a + b, bar, baz}
myCSV :: String
myCSV = "a,b,bar,baz\n1,2,\"hello, world!\",true\n3,3,,f"
main :: Effect Unit
main = launchAff_ do
parser <- liftEffect $ CSV.Parse.make {}
stringifier <- liftEffect $ CSV.Stringify.make {}
input <- liftEffect $ Stream.readableFromString myCSV
liftEffect $ Stream.pipe input parser
records <- CSV.Parse.readAll parser
liftEffect $ for_ records \r -> CSV.Stringify.write $ atob r
liftEffect $ Stream.end stringifier
-- "ab,bar,baz\n3,\"hello, world!\",true\n6,,false"
csvString <- CSV.Stringify.readAll stringifier
pure unit
```
### Synchronous
```purescript
module Main where
import Prelude
import Effect (Effect)
import Effect.Class (liftEffect)
import Effect.Aff (launchAff_)
import Node.Stream (pipe)
import Node.Stream as Stream
import Node.Stream.CSV.Stringify as CSV.Stringify
import Node.Stream.CSV.Parse as CSV.Parse
type MyCSVType1 = {a :: Int, b :: Int, bar :: String, baz :: Boolean}
type MyCSVType2 = {ab :: Int, bar :: String, baz :: Boolean}
atob :: MyCSVType1 -> MyCSVType2
atob {a, b, bar, baz} = {ab: a + b, bar, baz}
myCSV :: String
myCSV = "a,b,bar,baz\n1,2,\"hello, world!\",true\n3,3,,f"
main :: Effect Unit
main = launchAff_ do
records :: Array MyCSVType1 <- CSV.Parse.parse myCSV
-- "ab,bar,baz\n3,\"hello, world!\",true\n6,,false"
csvString <- CSV.Stringify.stringify (atob <$> records)
pure unit
```

BIN
bun.lockb Executable file

Binary file not shown.

27
bun/fmt.js Normal file
View File

@ -0,0 +1,27 @@
/** @type {(parser: string, ps: string[]) => import("bun").Subprocess} */
const prettier = (parser, ps) =>
Bun.spawn(["bun", "x", "prettier", "--write", "--parser", parser, ...ps], {
stdout: "inherit",
stderr: "inherit",
});
const procs = [
prettier("babel", ["./src/**/*.js", "./bun/**/*.js", "./.prettierrc.cjs"]),
prettier("json", ["./package.json", "./jsconfig.json"]),
Bun.spawn(
[
"bun",
"x",
"purs-tidy",
"format-in-place",
"src/**/*.purs",
"test/**/*.purs",
],
{
stdout: "inherit",
stderr: "inherit",
},
),
];
await Promise.all(procs.map((p) => p.exited));

34
bun/prepare.js Normal file
View File

@ -0,0 +1,34 @@
import { readFile, writeFile } from "fs/promises";
import { execSync } from "child_process";
let ver = process.argv[2];
if (!ver) {
console.error(`tag required: bun bun/prepare.js v1.0.0`);
process.exit(1);
} else if (!/v\d+\.\d+\.\d+/.test(ver)) {
console.error(`invalid tag: ${ver}`);
process.exit(1);
}
ver = (/\d+\.\d+\.\d+/.exec(ver) || [])[0] || "";
const pkg = await readFile("./package.json", "utf8");
const pkgnew = pkg.replace(/"version": ".+"/, `"version": "v${ver}"`);
await writeFile("./package.json", pkgnew);
const spago = await readFile("./spago.yaml", "utf8");
const spagonew = spago.replace(/version: .+/, `version: '${ver}'`);
await writeFile("./spago.yaml", spagonew);
const readme = await readFile("./README.md", "utf8");
const readmenew = readme.replace(
/packages\/purescript-node-stream-pipes\/.+?\//g,
`/packages/purescript-node-stream-pipes/${ver}/`,
);
await writeFile("./README.md", readmenew);
execSync(`git add spago.yaml package.json README.md`);
execSync(`git commit -m 'chore: prepare v${ver}'`);
execSync(`git tag v${ver}`);
execSync(`git push --tags`);
execSync(`git push --mirror github-mirror`);

16
jsconfig.json Normal file
View File

@ -0,0 +1,16 @@
{
"compilerOptions": {
"types": ["bun-types"],
"lib": ["esnext"],
"target": "esnext",
"module": "esnext",
"moduleResolution": "bundler",
"moduleDetection": "force",
"jsx": "react",
"allowJs": true,
"checkJs": true,
"noEmit": true,
"strict": true
},
"include": ["src/**/*.js", "bun/**/*.js"]
}

11
package.json Normal file
View File

@ -0,0 +1,11 @@
{
"name": "purescript-csv-stream",
"version": "v1.2.19",
"dependencies": {
"csv-parse": "^5.5.5",
"csv-stringify": "^6.4.6"
},
"devDependencies": {
"typescript": "^5.4.5"
}
}

903
spago.lock Normal file
View File

@ -0,0 +1,903 @@
workspace:
packages:
node-stream-pipes:
path: ./
dependencies:
- aff: ">=7.1.0 <8.0.0"
- control: ">=6.0.0 <7.0.0"
- effect: ">=4.0.0 <5.0.0"
- either: ">=6.1.0 <7.0.0"
- exceptions: ">=6.0.0 <7.0.0"
- maybe: ">=6.0.0 <7.0.0"
- newtype: ">=5.0.0 <6.0.0"
- node-buffer: ">=9.0.0 <10.0.0"
- node-event-emitter: ">=3.0.0 <4.0.0"
- node-streams: ">=9.0.0 <10.0.0"
- pipes: ">=8.0.0 <9.0.0"
- prelude: ">=6.0.1 <7.0.0"
- tailrec: ">=6.1.0 <7.0.0"
- transformers: ">=6.0.0 <7.0.0"
- unsafe-coerce: ">=6.0.0 <7.0.0"
test_dependencies:
- console
- gen
- node-fs
- node-zlib
- quickcheck
- simple-json
- spec
build_plan:
- aff
- ansi
- arraybuffer-types
- arrays
- avar
- bifunctors
- catenable-lists
- console
- const
- contravariant
- control
- datetime
- distributive
- effect
- either
- enums
- exceptions
- exists
- foldable-traversable
- foreign
- foreign-object
- fork
- free
- functions
- functors
- gen
- identity
- integers
- invariant
- js-date
- lazy
- lcg
- lists
- maybe
- mmorph
- newtype
- node-buffer
- node-event-emitter
- node-fs
- node-path
- node-streams
- node-zlib
- nonempty
- now
- nullable
- numbers
- ordered-collections
- orders
- parallel
- partial
- pipes
- prelude
- profunctor
- quickcheck
- random
- record
- refs
- safe-coerce
- simple-json
- spec
- st
- strings
- tailrec
- transformers
- tuples
- type-equality
- typelevel-prelude
- unfoldable
- unsafe-coerce
- variant
extra_packages: {}
packages:
aff:
type: registry
version: 7.1.0
integrity: sha256-7hOC6uQO9XBAI5FD8F33ChLjFAiZVfd4BJMqlMh7TNU=
dependencies:
- arrays
- bifunctors
- control
- datetime
- effect
- either
- exceptions
- foldable-traversable
- functions
- maybe
- newtype
- parallel
- prelude
- refs
- tailrec
- transformers
- unsafe-coerce
ansi:
type: registry
version: 7.0.0
integrity: sha256-ZMB6HD+q9CXvn9fRCmJ8dvuDrOVHcjombL3oNOerVnE=
dependencies:
- foldable-traversable
- lists
- strings
arraybuffer-types:
type: registry
version: 3.0.2
integrity: sha256-mQKokysYVkooS4uXbO+yovmV/s8b138Ws3zQvOwIHRA=
dependencies: []
arrays:
type: registry
version: 7.3.0
integrity: sha256-tmcklBlc/muUtUfr9RapdCPwnlQeB3aSrC4dK85gQlc=
dependencies:
- bifunctors
- control
- foldable-traversable
- functions
- maybe
- nonempty
- partial
- prelude
- safe-coerce
- st
- tailrec
- tuples
- unfoldable
- unsafe-coerce
avar:
type: registry
version: 5.0.0
integrity: sha256-e7hf0x4hEpcygXP0LtvfvAQ49Bbj2aWtZT3gqM///0A=
dependencies:
- aff
- effect
- either
- exceptions
- functions
- maybe
bifunctors:
type: registry
version: 6.0.0
integrity: sha256-/gZwC9YhNxZNQpnHa5BIYerCGM2jeX9ukZiEvYxm5Nw=
dependencies:
- const
- either
- newtype
- prelude
- tuples
catenable-lists:
type: registry
version: 7.0.0
integrity: sha256-76vYENhwF4BWTBsjeLuErCH2jqVT4M3R1HX+4RwSftA=
dependencies:
- control
- foldable-traversable
- lists
- maybe
- prelude
- tuples
- unfoldable
console:
type: registry
version: 6.1.0
integrity: sha256-CxmAzjgyuGDmt9FZW51VhV6rBPwR6o0YeKUzA9rSzcM=
dependencies:
- effect
- prelude
const:
type: registry
version: 6.0.0
integrity: sha256-tNrxDW8D8H4jdHE2HiPzpLy08zkzJMmGHdRqt5BQuTc=
dependencies:
- invariant
- newtype
- prelude
contravariant:
type: registry
version: 6.0.0
integrity: sha256-TP+ooAp3vvmdjfQsQJSichF5B4BPDHp3wAJoWchip6c=
dependencies:
- const
- either
- newtype
- prelude
- tuples
control:
type: registry
version: 6.0.0
integrity: sha256-sH7Pg9E96JCPF9PIA6oQ8+BjTyO/BH1ZuE/bOcyj4Jk=
dependencies:
- newtype
- prelude
datetime:
type: registry
version: 6.1.0
integrity: sha256-g/5X5BBegQWLpI9IWD+sY6mcaYpzzlW5lz5NBzaMtyI=
dependencies:
- bifunctors
- control
- either
- enums
- foldable-traversable
- functions
- gen
- integers
- lists
- maybe
- newtype
- numbers
- ordered-collections
- partial
- prelude
- tuples
distributive:
type: registry
version: 6.0.0
integrity: sha256-HTDdmEnzigMl+02SJB88j+gAXDx9VKsbvR4MJGDPbOQ=
dependencies:
- identity
- newtype
- prelude
- tuples
- type-equality
effect:
type: registry
version: 4.0.0
integrity: sha256-eBtZu+HZcMa5HilvI6kaDyVX3ji8p0W9MGKy2K4T6+M=
dependencies:
- prelude
either:
type: registry
version: 6.1.0
integrity: sha256-6hgTPisnMWVwQivOu2PKYcH8uqjEOOqDyaDQVUchTpY=
dependencies:
- control
- invariant
- maybe
- prelude
enums:
type: registry
version: 6.0.1
integrity: sha256-HWaD73JFLorc4A6trKIRUeDMdzE+GpkJaEOM1nTNkC8=
dependencies:
- control
- either
- gen
- maybe
- newtype
- nonempty
- partial
- prelude
- tuples
- unfoldable
exceptions:
type: registry
version: 6.0.0
integrity: sha256-y/xTAEIZIARCE+50/u1di0ncebJ+CIwNOLswyOWzMTw=
dependencies:
- effect
- either
- maybe
- prelude
exists:
type: registry
version: 6.0.0
integrity: sha256-A0JQHpTfo1dNOj9U5/Fd3xndlRSE0g2IQWOGor2yXn8=
dependencies:
- unsafe-coerce
foldable-traversable:
type: registry
version: 6.0.0
integrity: sha256-fLeqRYM4jUrZD5H4WqcwUgzU7XfYkzO4zhgtNc3jcWM=
dependencies:
- bifunctors
- const
- control
- either
- functors
- identity
- maybe
- newtype
- orders
- prelude
- tuples
foreign:
type: registry
version: 7.0.0
integrity: sha256-1ORiqoS3HW+qfwSZAppHPWy4/6AQysxZ2t29jcdUMNA=
dependencies:
- either
- functions
- identity
- integers
- lists
- maybe
- prelude
- strings
- transformers
foreign-object:
type: registry
version: 4.1.0
integrity: sha256-q24okj6mT+yGHYQ+ei/pYPj5ih6sTbu7eDv/WU56JVo=
dependencies:
- arrays
- foldable-traversable
- functions
- gen
- lists
- maybe
- prelude
- st
- tailrec
- tuples
- typelevel-prelude
- unfoldable
fork:
type: registry
version: 6.0.0
integrity: sha256-X7u0SuCvFbLbzuNEKLBNuWjmcroqMqit4xEzpQwAP7E=
dependencies:
- aff
free:
type: registry
version: 7.1.0
integrity: sha256-JAumgEsGSzJCNLD8AaFvuX7CpqS5yruCngi6yI7+V5k=
dependencies:
- catenable-lists
- control
- distributive
- either
- exists
- foldable-traversable
- invariant
- lazy
- maybe
- prelude
- tailrec
- transformers
- tuples
- unsafe-coerce
functions:
type: registry
version: 6.0.0
integrity: sha256-adMyJNEnhGde2unHHAP79gPtlNjNqzgLB8arEOn9hLI=
dependencies:
- prelude
functors:
type: registry
version: 5.0.0
integrity: sha256-zfPWWYisbD84MqwpJSZFlvM6v86McM68ob8p9s27ywU=
dependencies:
- bifunctors
- const
- contravariant
- control
- distributive
- either
- invariant
- maybe
- newtype
- prelude
- profunctor
- tuples
- unsafe-coerce
gen:
type: registry
version: 4.0.0
integrity: sha256-f7yzAXWwr+xnaqEOcvyO3ezKdoes8+WXWdXIHDBCAPI=
dependencies:
- either
- foldable-traversable
- identity
- maybe
- newtype
- nonempty
- prelude
- tailrec
- tuples
- unfoldable
identity:
type: registry
version: 6.0.0
integrity: sha256-4wY0XZbAksjY6UAg99WkuKyJlQlWAfTi2ssadH0wVMY=
dependencies:
- control
- invariant
- newtype
- prelude
integers:
type: registry
version: 6.0.0
integrity: sha256-sf+sK26R1hzwl3NhXR7WAu9zCDjQnfoXwcyGoseX158=
dependencies:
- maybe
- numbers
- prelude
invariant:
type: registry
version: 6.0.0
integrity: sha256-RGWWyYrz0Hs1KjPDA+87Kia67ZFBhfJ5lMGOMCEFoLo=
dependencies:
- control
- prelude
js-date:
type: registry
version: 8.0.0
integrity: sha256-6TVF4DWg5JL+jRAsoMssYw8rgOVALMUHT1CuNZt8NRo=
dependencies:
- datetime
- effect
- exceptions
- foreign
- integers
- now
lazy:
type: registry
version: 6.0.0
integrity: sha256-lMsfFOnlqfe4KzRRiW8ot5ge6HtcU3Eyh2XkXcP5IgU=
dependencies:
- control
- foldable-traversable
- invariant
- prelude
lcg:
type: registry
version: 4.0.0
integrity: sha256-h7ME5cthLfbgJOJdsZcSfFpwXsx4rf8YmhebU+3iSYg=
dependencies:
- effect
- integers
- maybe
- partial
- prelude
- random
lists:
type: registry
version: 7.0.0
integrity: sha256-EKF15qYqucuXP2lT/xPxhqy58f0FFT6KHdIB/yBOayI=
dependencies:
- bifunctors
- control
- foldable-traversable
- lazy
- maybe
- newtype
- nonempty
- partial
- prelude
- tailrec
- tuples
- unfoldable
maybe:
type: registry
version: 6.0.0
integrity: sha256-5cCIb0wPwbat2PRkQhUeZO0jcAmf8jCt2qE0wbC3v2Q=
dependencies:
- control
- invariant
- newtype
- prelude
mmorph:
type: registry
version: 7.0.0
integrity: sha256-urZlZNNqGeQFe5D/ClHlR8QgGBNHTMFPtJ5S5IpflTQ=
dependencies:
- free
- functors
- transformers
newtype:
type: registry
version: 5.0.0
integrity: sha256-gdrQu8oGe9eZE6L3wOI8ql/igOg+zEGB5ITh2g+uttw=
dependencies:
- prelude
- safe-coerce
node-buffer:
type: registry
version: 9.0.0
integrity: sha256-PWE2DJ5ruBLCmeA/fUiuySEFmUJ/VuRfyrnCuVZBlu4=
dependencies:
- arraybuffer-types
- effect
- maybe
- nullable
- st
- unsafe-coerce
node-event-emitter:
type: registry
version: 3.0.0
integrity: sha256-Qw0MjsT4xRH2j2i4K8JmRjcMKnH5z1Cw39t00q4LE4w=
dependencies:
- effect
- either
- functions
- maybe
- nullable
- prelude
- unsafe-coerce
node-fs:
type: registry
version: 9.1.0
integrity: sha256-TzhvGdrwcM0bazDvrWSqh+M/H8GKYf1Na6aGm2Qg4+c=
dependencies:
- datetime
- effect
- either
- enums
- exceptions
- functions
- integers
- js-date
- maybe
- node-buffer
- node-path
- node-streams
- nullable
- partial
- prelude
- strings
- unsafe-coerce
node-path:
type: registry
version: 5.0.0
integrity: sha256-pd82nQ+2l5UThzaxPdKttgDt7xlsgIDLpPG0yxDEdyE=
dependencies:
- effect
node-streams:
type: registry
version: 9.0.0
integrity: sha256-2n6dq7YWleTDmD1Kur/ul7Cn08IvWrScgPf+0PgX2TQ=
dependencies:
- aff
- effect
- either
- exceptions
- node-buffer
- node-event-emitter
- nullable
- prelude
node-zlib:
type: registry
version: 0.4.0
integrity: sha256-kYSajFQFzWVg71l5/y4w4kXdTr5EJoqyV3D2RqmAjQ4=
dependencies:
- aff
- effect
- either
- functions
- node-buffer
- node-streams
- prelude
- unsafe-coerce
nonempty:
type: registry
version: 7.0.0
integrity: sha256-54ablJZUHGvvlTJzi3oXyPCuvY6zsrWJuH/dMJ/MFLs=
dependencies:
- control
- foldable-traversable
- maybe
- prelude
- tuples
- unfoldable
now:
type: registry
version: 6.0.0
integrity: sha256-xZ7x37ZMREfs6GCDw/h+FaKHV/3sPWmtqBZRGTxybQY=
dependencies:
- datetime
- effect
nullable:
type: registry
version: 6.0.0
integrity: sha256-yiGBVl3AD+Guy4kNWWeN+zl1gCiJK+oeIFtZtPCw4+o=
dependencies:
- effect
- functions
- maybe
numbers:
type: registry
version: 9.0.1
integrity: sha256-/9M6aeMDBdB4cwYDeJvLFprAHZ49EbtKQLIJsneXLIk=
dependencies:
- functions
- maybe
ordered-collections:
type: registry
version: 3.2.0
integrity: sha256-o9jqsj5rpJmMdoe/zyufWHFjYYFTTsJpgcuCnqCO6PM=
dependencies:
- arrays
- foldable-traversable
- gen
- lists
- maybe
- partial
- prelude
- st
- tailrec
- tuples
- unfoldable
orders:
type: registry
version: 6.0.0
integrity: sha256-nBA0g3/ai0euH8q9pSbGqk53W2q6agm/dECZTHcoink=
dependencies:
- newtype
- prelude
parallel:
type: registry
version: 6.0.0
integrity: sha256-VJbkGD0rAKX+NUEeBJbYJ78bEKaZbgow+QwQEfPB6ko=
dependencies:
- control
- effect
- either
- foldable-traversable
- functors
- maybe
- newtype
- prelude
- profunctor
- refs
- transformers
partial:
type: registry
version: 4.0.0
integrity: sha256-fwXerld6Xw1VkReh8yeQsdtLVrjfGiVuC5bA1Wyo/J4=
dependencies: []
pipes:
type: registry
version: 8.0.0
integrity: sha256-kvfqGM4cPA/wCcBHbp5psouFw5dZGvku2462x7ZBwSY=
dependencies:
- aff
- lists
- mmorph
- prelude
- tailrec
- transformers
- tuples
prelude:
type: registry
version: 6.0.1
integrity: sha256-o8p6SLYmVPqzXZhQFd2hGAWEwBoXl1swxLG/scpJ0V0=
dependencies: []
profunctor:
type: registry
version: 6.0.1
integrity: sha256-E58hSYdJvF2Qjf9dnWLPlJKh2Z2fLfFLkQoYi16vsFk=
dependencies:
- control
- distributive
- either
- exists
- invariant
- newtype
- prelude
- tuples
quickcheck:
type: registry
version: 8.0.1
integrity: sha256-ZvpccKQCvgslTXZCNmpYW4bUsFzhZd/kQUr2WmxFTGY=
dependencies:
- arrays
- console
- control
- effect
- either
- enums
- exceptions
- foldable-traversable
- gen
- identity
- integers
- lazy
- lcg
- lists
- maybe
- newtype
- nonempty
- numbers
- partial
- prelude
- record
- st
- strings
- tailrec
- transformers
- tuples
- unfoldable
random:
type: registry
version: 6.0.0
integrity: sha256-CJ611a35MPCE7XQMp0rdC6MCn76znlhisiCRgboAG+Q=
dependencies:
- effect
- integers
record:
type: registry
version: 4.0.0
integrity: sha256-Za5U85bTRJEfGK5Sk4hM41oXy84YQI0I8TL3WUn1Qzg=
dependencies:
- functions
- prelude
- unsafe-coerce
refs:
type: registry
version: 6.0.0
integrity: sha256-Vgwne7jIbD3ZMoLNNETLT8Litw6lIYo3MfYNdtYWj9s=
dependencies:
- effect
- prelude
safe-coerce:
type: registry
version: 2.0.0
integrity: sha256-a1ibQkiUcbODbLE/WAq7Ttbbh9ex+x33VCQ7GngKudU=
dependencies:
- unsafe-coerce
simple-json:
type: registry
version: 9.0.0
integrity: sha256-K3RJaThqsszTd+TEklzZmAdDqvIHWgXIfKqlsoykU1c=
dependencies:
- arrays
- exceptions
- foreign
- foreign-object
- nullable
- prelude
- record
- typelevel-prelude
- variant
spec:
type: registry
version: 7.6.0
integrity: sha256-+merGdQbL9zWONbnt8S8J9afGJ59MQqGtS0qSd3yu4I=
dependencies:
- aff
- ansi
- arrays
- avar
- bifunctors
- control
- datetime
- effect
- either
- exceptions
- foldable-traversable
- fork
- identity
- integers
- lists
- maybe
- newtype
- now
- ordered-collections
- parallel
- pipes
- prelude
- refs
- strings
- tailrec
- transformers
- tuples
st:
type: registry
version: 6.2.0
integrity: sha256-z9X0WsOUlPwNx9GlCC+YccCyz8MejC8Wb0C4+9fiBRY=
dependencies:
- partial
- prelude
- tailrec
- unsafe-coerce
strings:
type: registry
version: 6.0.1
integrity: sha256-WssD3DbX4OPzxSdjvRMX0yvc9+pS7n5gyPv5I2Trb7k=
dependencies:
- arrays
- control
- either
- enums
- foldable-traversable
- gen
- integers
- maybe
- newtype
- nonempty
- partial
- prelude
- tailrec
- tuples
- unfoldable
- unsafe-coerce
tailrec:
type: registry
version: 6.1.0
integrity: sha256-Xx19ECVDRrDWpz9D2GxQHHV89vd61dnXxQm0IcYQHGk=
dependencies:
- bifunctors
- effect
- either
- identity
- maybe
- partial
- prelude
- refs
transformers:
type: registry
version: 6.0.0
integrity: sha256-Pzw40HjthX77tdPAYzjx43LK3X5Bb7ZspYAp27wksFA=
dependencies:
- control
- distributive
- effect
- either
- exceptions
- foldable-traversable
- identity
- lazy
- maybe
- newtype
- prelude
- tailrec
- tuples
- unfoldable
tuples:
type: registry
version: 7.0.0
integrity: sha256-1rXgTomes9105BjgXqIw0FL6Fz1lqqUTLWOumhWec1M=
dependencies:
- control
- invariant
- prelude
type-equality:
type: registry
version: 4.0.1
integrity: sha256-Hs9D6Y71zFi/b+qu5NSbuadUQXe5iv5iWx0226vOHUw=
dependencies: []
typelevel-prelude:
type: registry
version: 7.0.0
integrity: sha256-uFF2ph+vHcQpfPuPf2a3ukJDFmLhApmkpTMviHIWgJM=
dependencies:
- prelude
- type-equality
unfoldable:
type: registry
version: 6.0.0
integrity: sha256-JtikvJdktRap7vr/K4ITlxUX1QexpnqBq0G/InLr6eg=
dependencies:
- foldable-traversable
- maybe
- partial
- prelude
- tuples
unsafe-coerce:
type: registry
version: 6.0.0
integrity: sha256-IqIYW4Vkevn8sI+6aUwRGvd87tVL36BBeOr0cGAE7t0=
dependencies: []
variant:
type: registry
version: 8.0.0
integrity: sha256-SR//zQDg2dnbB8ZHslcxieUkCeNlbMToapvmh9onTtw=
dependencies:
- enums
- lists
- maybe
- partial
- prelude
- record
- tuples
- unsafe-coerce

39
spago.yaml Normal file
View File

@ -0,0 +1,39 @@
package:
name: node-stream-pipes
publish:
version: '1.2.19'
license: 'GPL-3.0-or-later'
location:
githubOwner: 'cakekindel'
githubRepo: 'purescript-node-stream-pipes'
build:
strict: true
pedanticPackages: true
dependencies:
- aff: ">=7.1.0 <8.0.0"
- control: ">=6.0.0 <7.0.0"
- effect: ">=4.0.0 <5.0.0"
- either: ">=6.1.0 <7.0.0"
- exceptions: ">=6.0.0 <7.0.0"
- maybe: ">=6.0.0 <7.0.0"
- newtype: ">=5.0.0 <6.0.0"
- node-buffer: ">=9.0.0 <10.0.0"
- node-event-emitter: ">=3.0.0 <4.0.0"
- node-streams: ">=9.0.0 <10.0.0"
- pipes: ">=8.0.0 <9.0.0"
- prelude: ">=6.0.1 <7.0.0"
- tailrec: ">=6.1.0 <7.0.0"
- transformers: ">=6.0.0 <7.0.0"
- unsafe-coerce: ">=6.0.0 <7.0.0"
test:
main: Test.Main
dependencies:
- console
- gen
- node-fs
- node-zlib
- quickcheck
- simple-json
- spec
workspace:
extraPackages: {}

49
src/Node.Stream.Object.js Normal file
View File

@ -0,0 +1,49 @@
import Stream from "stream";
/** @type {(s: Stream.Readable | Stream.Transform) => () => boolean} */
export const isReadableImpl = s => () => s.readable
/** @type {(s: Stream.Writable | Stream.Readable) => () => boolean} */
export const isClosedImpl = s => () => s.closed
/** @type {(s: Stream.Writable | Stream.Transform) => () => boolean} */
export const isWritableImpl = s => () => s.writable
/** @type {(s: Stream.Readable | Stream.Transform) => () => boolean} */
export const isReadableEndedImpl = s => () => s.readableEnded
/** @type {(s: Stream.Writable | Stream.Transform) => () => boolean} */
export const isWritableEndedImpl = s => () => s.writableEnded
/** @type {(s: Stream.Writable | Stream.Transform) => () => void} */
export const endImpl = (s) => () => s.end();
/** @type {<WriteResult>(o: {ok: WriteResult, wouldBlock: WriteResult, closed: WriteResult}) => (s: Stream.Writable | Stream.Transform) => (a: unknown) => () => WriteResult} */
export const writeImpl = ({ok, wouldBlock, closed}) => (s) => (a) => () => {
if (s.closed || s.writableEnded) {
return closed
}
if (s.write(a)) {
return ok
} else {
return wouldBlock
}
}
/** @type {<ReadResult>(o: {just: (_a: unknown) => ReadResult, wouldBlock: ReadResult, closed: ReadResult}) => (s: Stream.Readable | Stream.Transform) => () => ReadResult} */
export const readImpl =
({ just, closed, wouldBlock }) =>
(s) =>
() => {
if (s.closed || s.readableEnded) {
return closed;
}
const a = s.read();
if (a === null) {
return wouldBlock;
} else {
return just(a);
}
};

168
src/Node.Stream.Object.purs Normal file
View File

@ -0,0 +1,168 @@
module Node.Stream.Object where
import Prelude
import Data.Either (Either(..))
import Effect (Effect)
import Effect.Aff (Aff, effectCanceler, makeAff)
import Effect.Class (liftEffect)
import Effect.Exception (Error)
import Effect.Uncurried (mkEffectFn1)
import Node.Buffer (Buffer)
import Node.EventEmitter (EventHandle(..))
import Node.EventEmitter as Event
import Node.EventEmitter.UtilTypes (EventHandle0, EventHandle1)
import Node.Stream as Stream
import Unsafe.Coerce (unsafeCoerce)
data ReadResult a
= ReadWouldBlock
| ReadClosed
| ReadJust a
data WriteResult
= WriteWouldBlock
| WriteClosed
| WriteOk
type ReadResultFFI a = { closed :: ReadResult a, wouldBlock :: ReadResult a, just :: a -> ReadResult a }
type WriteResultFFI = { closed :: WriteResult, wouldBlock :: WriteResult, ok :: WriteResult }
foreign import data Writable :: Type -> Type
foreign import data Readable :: Type -> Type
foreign import data Transform :: Type -> Type -> Type
foreign import endImpl :: forall s. s -> Effect Unit
foreign import writeImpl :: forall s a. WriteResultFFI -> s -> a -> Effect WriteResult
foreign import readImpl :: forall s a. ReadResultFFI a -> s -> Effect (ReadResult a)
foreign import isReadableImpl :: forall s. s -> Effect Boolean
foreign import isWritableImpl :: forall s. s -> Effect Boolean
foreign import isReadableEndedImpl :: forall s. s -> Effect Boolean
foreign import isWritableEndedImpl :: forall s. s -> Effect Boolean
foreign import isClosedImpl :: forall s. s -> Effect Boolean
readResultFFI :: forall a. ReadResultFFI a
readResultFFI = {closed: ReadClosed, wouldBlock: ReadWouldBlock, just: ReadJust}
writeResultFFI :: WriteResultFFI
writeResultFFI = {closed: WriteClosed, wouldBlock: WriteWouldBlock, ok: WriteOk}
class Stream :: Type -> Constraint
class Stream s where
isClosed :: s -> Effect Boolean
instance Stream (Readable a) where
isClosed = isClosedImpl
else instance Stream (Writable a) where
isClosed = isClosedImpl
else instance Stream (Transform a b) where
isClosed = isClosedImpl
else instance Stream s => Stream s where
isClosed s = isClosed s
class Stream s <= Read s a | s -> a where
isReadable :: s -> Effect Boolean
isReadableEnded :: s -> Effect Boolean
read :: s -> Effect (ReadResult a)
class Stream s <= Write s a | s -> a where
isWritable :: s -> Effect Boolean
isWritableEnded :: s -> Effect Boolean
write :: s -> a -> Effect WriteResult
end :: s -> Effect Unit
instance Read (Readable a) a where
isReadable = isReadableImpl
isReadableEnded = isReadableEndedImpl
read = readImpl readResultFFI
else instance Read (Transform a b) b where
isReadable = isReadableImpl
isReadableEnded = isReadableEndedImpl
read = readImpl readResultFFI
else instance (Read s a) => Read s a where
isReadable = isReadableImpl
isReadableEnded = isReadableEndedImpl
read s = read s
instance Write (Writable a) a where
isWritable = isWritableImpl
isWritableEnded = isWritableEndedImpl
write s = writeImpl writeResultFFI s
end = endImpl
else instance Write (Transform a b) a where
isWritable = isWritableImpl
isWritableEnded = isWritableEndedImpl
write s = writeImpl writeResultFFI s
end = endImpl
else instance (Write s a) => Write s a where
isWritable = isWritableImpl
isWritableEnded = isWritableEndedImpl
write s a = write s a
end s = end s
fromBufferReadable :: forall r. Stream.Readable r -> Readable Buffer
fromBufferReadable = unsafeCoerce
fromBufferTransform :: Stream.Duplex -> Transform Buffer Buffer
fromBufferTransform = unsafeCoerce
fromBufferWritable :: forall r. Stream.Writable r -> Writable Buffer
fromBufferWritable = unsafeCoerce
fromStringReadable :: forall r. Stream.Readable r -> Readable String
fromStringReadable = unsafeCoerce
fromStringTransform :: Stream.Duplex -> Transform String String
fromStringTransform = unsafeCoerce
fromStringWritable :: forall r. Stream.Writable r -> Writable String
fromStringWritable = unsafeCoerce
awaitReadableOrClosed :: forall s a. Read s a => s -> Aff Unit
awaitReadableOrClosed s = do
closed <- liftEffect $ isClosed s
ended <- liftEffect $ isReadableEnded s
readable <- liftEffect $ isReadable s
when (not ended && not closed && not readable) $ makeAff \res -> do
cancelClose <- Event.once closeH (res $ Right unit) s
cancelError <- Event.once errorH (res <<< Left) s
cancelReadable <- flip (Event.once readableH) s do
cancelClose
cancelError
res $ Right unit
pure $ effectCanceler do
cancelReadable
cancelClose
cancelError
awaitWritableOrClosed :: forall s a. Write s a => s -> Aff Unit
awaitWritableOrClosed s = do
closed <- liftEffect $ isClosed s
ended <- liftEffect $ isWritableEnded s
writable <- liftEffect $ isWritable s
when (not closed && not ended && not writable) $ makeAff \res -> do
cancelClose <- Event.once closeH (res $ Right unit) s
cancelError <- Event.once errorH (res <<< Left) s
cancelDrain <- flip (Event.once drainH) s do
cancelClose
cancelError
res $ Right unit
pure $ effectCanceler do
cancelDrain
cancelClose
cancelError
readableH :: forall s a. Read s a => EventHandle0 s
readableH = EventHandle "readable" identity
drainH :: forall s a. Write s a => EventHandle0 s
drainH = EventHandle "drain" identity
closeH :: forall s. Stream s => EventHandle0 s
closeH = EventHandle "close" identity
errorH :: forall s. Stream s => EventHandle1 s Error
errorH = EventHandle "error" mkEffectFn1
endH :: forall s a. Write s a => EventHandle0 s
endH = EventHandle "end" identity

1
src/Pipes.CSV.Parse.purs Normal file
View File

@ -0,0 +1 @@
module Pipes.CSV.Parse where

View File

@ -0,0 +1,95 @@
module Pipes.Node.Stream where
import Prelude
import Control.Alternative (empty)
import Control.Monad.Maybe.Trans (MaybeT(..), runMaybeT)
import Control.Monad.Rec.Class (whileJust)
import Control.Monad.Trans.Class (lift)
import Data.Maybe (Maybe(..))
import Data.Newtype (wrap)
import Effect.Aff (Aff, delay)
import Effect.Aff.Class (liftAff)
import Effect.Class (liftEffect)
import Node.Stream.Object as O
import Pipes (await, yield)
import Pipes.Core (Consumer, Pipe, Producer)
import Pipes.Internal (Proxy)
import Pipes.Internal as P.I
type ProxyFFI :: Type -> Type -> Type -> Type -> Type -> Type -> Type
type ProxyFFI a' a b' b r pipe =
{ pure :: r -> pipe
, request :: a' -> (a -> pipe) -> pipe
, respond :: b -> (b' -> pipe) -> pipe
}
proxyFFI :: forall m a' a b' b r. ProxyFFI a' a b' b r (Proxy a' a b' b m r)
proxyFFI = { pure: P.I.Pure, request: P.I.Request, respond: P.I.Respond }
fromReadable :: forall s a. O.Read s a => s -> Producer (Maybe a) Aff Unit
fromReadable r = whileJust do
liftAff $ delay $ wrap 0.0
a <- liftEffect $ O.read r
case a of
O.ReadWouldBlock -> do
lift $ O.awaitReadableOrClosed r
pure $ Just unit
O.ReadClosed -> do
yield Nothing
pure Nothing
O.ReadJust a' -> do
yield $ Just a'
pure $ Just unit
fromWritable :: forall s a. O.Write s a => s -> Consumer (Maybe a) Aff Unit
fromWritable w = do
whileJust $ runMaybeT do
liftAff $ delay $ wrap 0.0
a <- MaybeT await
res <- liftEffect $ O.write w a
case res of
O.WriteClosed -> empty
O.WriteOk -> pure unit
O.WriteWouldBlock -> do
liftAff $ O.awaitWritableOrClosed w
pure unit
liftEffect $ O.end w
fromTransform :: forall a b. O.Transform a b -> Pipe (Maybe a) (Maybe b) Aff Unit
fromTransform t =
let
read' {exitOnWouldBlock} =
whileJust $ runMaybeT do
liftAff $ delay $ wrap 0.0
res <- liftEffect $ O.read t
case res of
O.ReadWouldBlock ->
if exitOnWouldBlock then do
empty
else do
liftAff $ O.awaitReadableOrClosed t
pure unit
O.ReadJust b -> do
lift $ yield $ Just b
pure unit
O.ReadClosed -> do
lift $ yield Nothing
empty
in do
whileJust $ runMaybeT do
liftAff $ delay $ wrap 0.0
a <- MaybeT await
writeRes <- liftEffect $ O.write t a
lift $ read' {exitOnWouldBlock: true}
case writeRes of
O.WriteOk -> pure unit
O.WriteClosed -> empty
O.WriteWouldBlock -> do
liftAff $ O.awaitWritableOrClosed t
pure unit
liftEffect $ O.end t
read' {exitOnWouldBlock: false}

14
test/Test/Main.purs Normal file
View File

@ -0,0 +1,14 @@
module Test.Main where
import Prelude
import Data.Maybe (Maybe(..))
import Effect (Effect)
import Effect.Aff (launchAff_)
import Test.Pipes.Node.Stream as Test.Pipes.Node.Stream
import Test.Spec.Reporter (consoleReporter, specReporter)
import Test.Spec.Runner (defaultConfig, runSpec')
main :: Effect Unit
main = launchAff_ $ runSpec' (defaultConfig { timeout = Nothing }) [ specReporter ] do
Test.Pipes.Node.Stream.spec

View File

@ -0,0 +1,4 @@
import Stream from 'stream'
/** @type {(a: Array<unknown>) => Stream.Readable}*/
export const readableFromArray = a => Stream.Readable.from(a)

View File

@ -0,0 +1,158 @@
module Test.Pipes.Node.Stream where
import Prelude
import Control.Monad.Error.Class (liftEither, try)
import Control.Monad.Morph (hoist)
import Control.Monad.Trans.Class (lift)
import Data.Array as Array
import Data.Bifunctor (lmap)
import Data.Foldable (fold, intercalate)
import Data.List ((:))
import Data.List as List
import Data.Maybe (Maybe(..), fromMaybe)
import Data.Newtype (wrap)
import Data.String.Gen (genAlphaString)
import Data.Traversable (for_, traverse)
import Data.Tuple (fst)
import Data.Tuple.Nested (type (/\), (/\))
import Effect (Effect)
import Effect.Aff (Aff, bracket, delay)
import Effect.Class (liftEffect)
import Effect.Exception (error)
import Node.Buffer (Buffer)
import Node.Buffer as Buffer
import Node.Encoding (Encoding(..))
import Node.FS.Stream as FS.Stream
import Node.FS.Sync as FS
import Node.Stream.Object as O
import Node.Zlib as Zlib
import Pipes (yield, (>->))
import Pipes.Core (Consumer, Producer, Pipe, runEffect)
import Pipes.Node.Stream as S
import Pipes.Prelude as Pipe
import Simple.JSON (class ReadForeign, class WriteForeign, readJSON, writeJSON)
import Test.QuickCheck.Arbitrary (arbitrary)
import Test.QuickCheck.Gen (randomSample', randomSampleOne, resize)
import Test.Spec (Spec, around, describe, it)
import Test.Spec.Assertions (shouldEqual)
foreign import readableFromArray :: forall @a. Array a -> O.Readable a
str2buf :: Pipe (Maybe String) (Maybe Buffer) Aff Unit
str2buf = hoist liftEffect $ Pipe.mapM (traverse $ flip Buffer.fromString UTF8)
buf2str :: Pipe (Maybe Buffer) (Maybe String) Aff Unit
buf2str = hoist liftEffect $ Pipe.mapM (traverse $ Buffer.toString UTF8)
buf2hex :: Pipe (Maybe Buffer) (Maybe String) Aff Unit
buf2hex = hoist liftEffect $ Pipe.mapM (traverse $ Buffer.toString Hex)
jsonStringify :: forall a. WriteForeign a => Pipe (Maybe a) (Maybe String) Aff Unit
jsonStringify = Pipe.map (map writeJSON)
jsonParse :: forall @a. ReadForeign a => Pipe (Maybe String) (Maybe a) Aff Unit
jsonParse = Pipe.mapM (traverse (liftEither <<< lmap (error <<< show) <<< readJSON))
writer :: String -> Effect (Consumer (Maybe Buffer) Aff Unit)
writer a = S.fromWritable <$> O.fromBufferWritable <$> FS.Stream.createWriteStream a
reader :: String -> Effect (Producer (Maybe Buffer) Aff Unit)
reader a = S.fromReadable <$> O.fromBufferReadable <$> FS.Stream.createReadStream a
tmpFile :: (String -> Aff Unit) -> Aff Unit
tmpFile f = tmpFiles (f <<< fst)
tmpFiles :: (String /\ String -> Aff Unit) -> Aff Unit
tmpFiles =
let
acq = do
randa <- liftEffect $ randomSampleOne $ resize 10 genAlphaString
randb <- liftEffect $ randomSampleOne $ resize 10 genAlphaString
void $ try $ liftEffect $ FS.mkdir ".tmp"
pure $ ("tmp." <> randa) /\ ("tmp." <> randb)
rel (a /\ b) = liftEffect (try (FS.rm a) *> void (try $ FS.rm b))
in
bracket acq rel
spec :: Spec Unit
spec =
describe "Test.Pipes.Node.Stream" do
describe "Readable" do
describe "Readable.from(<Iterable>)" do
it "empty" do
vals <- List.catMaybes <$> (Pipe.toListM $ S.fromReadable $ readableFromArray @{ foo :: String } [])
vals `shouldEqual` List.Nil
it "singleton" do
vals <- List.catMaybes <$> (Pipe.toListM $ S.fromReadable $ readableFromArray @{ foo :: String } [ { foo: "1" } ])
vals `shouldEqual` ({ foo: "1" } : List.Nil)
it "many elements" do
let exp = (\n -> { foo: show n }) <$> Array.range 0 100
vals <- List.catMaybes <$> (Pipe.toListM $ S.fromReadable $ readableFromArray exp)
vals `shouldEqual` (List.fromFoldable exp)
describe "Writable" $ around tmpFile do
describe "fs.WriteStream" do
it "pipe to file" \p -> do
w <- S.fromWritable <$> O.fromBufferWritable <$> liftEffect (FS.Stream.createWriteStream p)
let
source = do
buf <- liftEffect $ Buffer.fromString "hello" UTF8
yield $ Just buf
yield Nothing
runEffect $ source >-> w
contents <- liftEffect $ FS.readTextFile UTF8 p
contents `shouldEqual` "hello"
it "async pipe to file" \p -> do
w <- S.fromWritable <$> O.fromBufferWritable <$> liftEffect (FS.Stream.createWriteStream p)
let
source = do
yield $ Just "hello, "
lift $ delay $ wrap 5.0
yield $ Just "world!"
lift $ delay $ wrap 5.0
yield $ Just " "
lift $ delay $ wrap 5.0
yield $ Just "this is a "
lift $ delay $ wrap 5.0
yield $ Just "test."
yield Nothing
runEffect $ source >-> str2buf >-> w
contents <- liftEffect $ FS.readTextFile UTF8 p
contents `shouldEqual` "hello, world! this is a test."
it "chained pipes" \p -> do
let
obj = do
str :: String <- genAlphaString
num :: Int <- arbitrary
stuff :: Array String <- arbitrary
pure {str, num, stuff}
objs <- liftEffect $ randomSample' 1 obj
let
exp = fold (writeJSON <$> objs)
objs' = for_ (Just <$> objs) yield *> yield Nothing
w <- liftEffect $ writer p
runEffect $ objs' >-> jsonStringify >-> str2buf >-> w
contents <- liftEffect $ FS.readTextFile UTF8 p
contents `shouldEqual` exp
describe "Transform" do
it "gzip" do
let
json = do
yield $ Just $ writeJSON {foo: "bar"}
yield Nothing
exp = "1f8b0800000000000003ab564acbcf57b2524a4a2c52aa0500eff52bfe0d000000"
gzip <- S.fromTransform <$> O.fromBufferTransform <$> liftEffect (Zlib.toDuplex <$> Zlib.createGzip)
outs :: List.List String <- List.catMaybes <$> Pipe.toListM (json >-> str2buf >-> gzip >-> buf2hex)
fold outs `shouldEqual` exp
around tmpFiles
$ it "file >-> gzip >-> file >-> gunzip" \(a /\ b) -> do
liftEffect $ FS.writeTextFile UTF8 a $ writeJSON [1, 2, 3, 4]
areader <- liftEffect $ reader a
bwriter <- liftEffect $ writer b
gzip <- S.fromTransform <$> O.fromBufferTransform <$> liftEffect (Zlib.toDuplex <$> Zlib.createGzip)
runEffect $ areader >-> gzip >-> bwriter
gunzip <- S.fromTransform <$> O.fromBufferTransform <$> liftEffect (Zlib.toDuplex <$> Zlib.createGunzip)
breader <- liftEffect $ reader b
nums <- Pipe.toListM (breader >-> gunzip >-> buf2str >-> jsonParse @(Array Int) >-> Pipe.mapFoldable (fromMaybe []))
Array.fromFoldable nums `shouldEqual` [1, 2, 3, 4]