├── TEST_FILE ├── FAKE_COMMIT_TEST_FILE ├── packages ├── fs │ ├── .npmignore │ ├── README.md │ ├── package.json │ ├── lib │ │ └── FS.ts │ └── __tests__ │ │ └── FS.test.ts ├── chai │ ├── .npmignore │ ├── .mocharc.yml │ ├── README.md │ ├── Chai.ts │ └── package.json ├── console │ ├── .npmignore │ ├── README.md │ ├── package.json │ ├── lib │ │ └── Console.ts │ └── __tests__ │ │ └── Console.test.ts ├── core │ ├── .npmignore │ ├── lib │ │ ├── internals │ │ │ ├── CBExit.ts │ │ │ ├── CB.ts │ │ │ ├── IDGenerator.ts │ │ │ ├── OnError.ts │ │ │ ├── FiberConfig.ts │ │ │ ├── Stack.ts │ │ │ ├── CancellationList.ts │ │ │ ├── PureMutableList.ts │ │ │ └── YieldStrategy.ts │ │ ├── main │ │ │ ├── Log.ts │ │ │ ├── Counter.ts │ │ │ ├── FS.ts │ │ │ ├── Flag.ts │ │ │ ├── Reservation.ts │ │ │ ├── Snapshot.ts │ │ │ ├── Ref.ts │ │ │ ├── Exit.ts │ │ │ ├── FMap.ts │ │ │ ├── Instructions.ts │ │ │ ├── Await.ts │ │ │ ├── Managed.ts │ │ │ ├── Queue.ts │ │ │ └── Chunk.ts │ │ └── runtimes │ │ │ ├── FiberRuntime.ts │ │ │ ├── DefaultRuntime.ts │ │ │ ├── IRuntime.ts │ │ │ └── TestRuntime.ts │ ├── __tests__ │ │ ├── internals │ │ │ ├── CreateArray.ts │ │ │ ├── CreateMockChunks.ts │ │ │ └── Resource.ts │ │ ├── YieldASAP.test.ts │ │ ├── Ref.test.ts │ │ ├── Queue.test.ts │ │ ├── Chunk.test.ts │ │ ├── Program.test.ts │ │ └── Await.test.ts │ ├── README.md │ ├── index.ts │ └── package.json ├── docs │ ├── .npmignore │ ├── core │ │ ├── qio.md │ │ ├── ref.md │ │ ├── await.md │ │ ├── queue.md │ │ ├── installation.md │ │ ├── cancellation.md │ │ ├── type-signature.md │ │ ├── comparison.md │ │ ├── brackets.md │ │ ├── exceptions-anomalies.md │ │ ├── managed.md │ │ ├── concurrency.md │ │ ├── usage.md │ │ ├── stack-safety.md │ │ ├── fiber.md │ │ ├── environment.md │ │ ├── multi-task.md │ │ └── effects.md │ ├── fs │ │ ├── usage.md │ │ └── installation.md │ ├── http │ │ ├── usage.md │ │ └── installation.md │ ├── stream │ │ ├── stream.md │ │ └── installation.md │ ├── README.md │ ├── others │ │ ├── credits.md │ │ ├── ecosystem.md │ │ └── benchmarks.md │ ├── console │ │ ├── installation.md │ │ └── usage.md │ ├── concepts │ │ ├── pure-functions.md │ │ ├── referential-transparency.md │ │ ├── introduction.md │ │ └── non-determinism.md │ ├── package.json │ └── comparison │ │ ├── features.md │ │ └── benchmarks.md ├── example │ ├── .npmignore │ ├── README.md │ ├── guess-the-number │ │ └── src │ │ │ ├── Run.ts │ │ │ └── Program.ts │ ├── fibonacci │ │ ├── Fibonacci.ts │ │ └── FibonacciIO.ts │ ├── package.json │ └── __tests__ │ │ └── Program.test.ts ├── http │ ├── .npmignore │ ├── README.md │ ├── package.json │ ├── __tests__ │ │ └── Http.test.ts │ └── lib │ │ └── Http.ts ├── prelude │ ├── .npmignore │ ├── T.ts │ ├── README.md │ ├── Noop.ts │ ├── index.ts │ ├── Id.ts │ ├── Range.ts │ ├── package.json │ └── CHANGELOG.md ├── scripts │ ├── .npmignore │ ├── README.md │ ├── package.json │ ├── lib │ │ └── Symlink.ts │ └── CHANGELOG.md ├── stream │ ├── .npmignore │ ├── index.ts │ ├── README.md │ └── package.json ├── website │ ├── .npmignore │ ├── static │ │ ├── img │ │ │ ├── favicon.ico │ │ │ └── oss_logo.png │ │ └── css │ │ │ └── custom.css │ ├── blog │ │ ├── 2017-09-26-adding-rss.md │ │ ├── 2017-10-24-new-version-1.0.0.md │ │ ├── 2017-09-25-testing-rss.md │ │ ├── 2016-03-11-blog-post.md │ │ └── 2017-04-10-blog-post-two.md │ ├── sidebars.json │ ├── package.json │ ├── pages │ │ └── en │ │ │ ├── users.js │ │ │ └── help.js │ ├── siteConfig.js │ ├── core │ │ └── Footer.js │ └── README.md ├── benchmarks │ ├── .npmignore │ ├── internals │ │ ├── Inc.ts │ │ ├── BENCHMARKS.tmpl.md │ │ ├── PrintLn.ts │ │ └── RunSuite.ts │ ├── README.md │ ├── run.sh │ ├── IO │ │ ├── Constant.ts │ │ ├── Map.ts │ │ ├── NestedMap.ts │ │ ├── NestedChain.ts │ │ └── Fibonacci.ts │ ├── DS │ │ └── Stack.ts │ ├── package.json │ └── Stream │ │ └── Stream.ts └── prototype │ ├── .npmignore │ ├── README.md │ ├── package.json │ └── FServer.ts ├── .dockerignore ├── .prettierignore ├── .prettierrc ├── scripts ├── docs.sh └── publish.sh ├── Global.d.ts ├── test.ts ├── .npmignore ├── lerna.json ├── .yo-rc.json ├── .gitignore ├── tsconfig.json ├── .travis.yml ├── typedoc.json ├── LICENSE.md ├── tslint.json ├── README.md └── package.json /TEST_FILE: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /FAKE_COMMIT_TEST_FILE: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/fs/.npmignore: -------------------------------------------------------------------------------- 1 | ../../.npmignore -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | */node_modules 2 | *.log 3 | -------------------------------------------------------------------------------- /packages/chai/.npmignore: -------------------------------------------------------------------------------- 1 | ../../.npmignore -------------------------------------------------------------------------------- /packages/console/.npmignore: -------------------------------------------------------------------------------- 1 | ../../.npmignore -------------------------------------------------------------------------------- /packages/core/.npmignore: -------------------------------------------------------------------------------- 1 | ../../.npmignore -------------------------------------------------------------------------------- /packages/docs/.npmignore: -------------------------------------------------------------------------------- 1 | ../../.npmignore -------------------------------------------------------------------------------- /packages/example/.npmignore: -------------------------------------------------------------------------------- 1 | ../../.npmignore -------------------------------------------------------------------------------- /packages/http/.npmignore: -------------------------------------------------------------------------------- 1 | ../../.npmignore -------------------------------------------------------------------------------- /packages/prelude/.npmignore: -------------------------------------------------------------------------------- 1 | ../../.npmignore -------------------------------------------------------------------------------- /packages/scripts/.npmignore: -------------------------------------------------------------------------------- 1 | ../../.npmignore -------------------------------------------------------------------------------- /packages/stream/.npmignore: -------------------------------------------------------------------------------- 1 | ../../.npmignore -------------------------------------------------------------------------------- /packages/website/.npmignore: -------------------------------------------------------------------------------- 1 | ../../.npmignore -------------------------------------------------------------------------------- /packages/benchmarks/.npmignore: -------------------------------------------------------------------------------- 1 | ../../.npmignore -------------------------------------------------------------------------------- /packages/prototype/.npmignore: -------------------------------------------------------------------------------- 1 | ../../.npmignore -------------------------------------------------------------------------------- /packages/prelude/T.ts: -------------------------------------------------------------------------------- 1 | export const T = () => true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | packages/fs/lib/internals/FnTypeOverride.ts 2 | -------------------------------------------------------------------------------- /packages/chai/.mocharc.yml: -------------------------------------------------------------------------------- 1 | require: 2 | - './packages/chai' 3 | -------------------------------------------------------------------------------- /packages/docs/core/qio.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: QIO 3 | --- 4 | 5 | # Coming soon ... 6 | -------------------------------------------------------------------------------- /packages/docs/core/ref.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Ref 3 | --- 4 | 5 | # Coming soon ... 6 | -------------------------------------------------------------------------------- /packages/benchmarks/internals/Inc.ts: -------------------------------------------------------------------------------- 1 | export const inc = (_: bigint) => _ + BigInt(1) 2 | -------------------------------------------------------------------------------- /packages/docs/core/await.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Await 3 | --- 4 | 5 | # Coming soon ... 6 | -------------------------------------------------------------------------------- /packages/docs/core/queue.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Queue 3 | --- 4 | 5 | # Coming soon ... 6 | -------------------------------------------------------------------------------- /packages/docs/fs/usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Usage 3 | --- 4 | 5 | # Coming soon ... 6 | -------------------------------------------------------------------------------- /packages/docs/http/usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Usage 3 | --- 4 | 5 | # Coming soon ... 6 | -------------------------------------------------------------------------------- /packages/docs/stream/stream.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Usage 3 | --- 4 | 5 | # Coming Soon... 6 | -------------------------------------------------------------------------------- /packages/prelude/README.md: -------------------------------------------------------------------------------- 1 | # `@qio/prelude` 2 | 3 | Used internally to make things work. 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": false, 3 | "semi": false, 4 | "singleQuote": true 5 | } -------------------------------------------------------------------------------- /scripts/docs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | yarn doc:typedoc 4 | yarn workspace @qio/website run build 5 | -------------------------------------------------------------------------------- /packages/prelude/Noop.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Empty function 3 | * @ignore 4 | */ 5 | export const noop = () => {} 6 | -------------------------------------------------------------------------------- /Global.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tushar on 2019-05-24 3 | */ 4 | 5 | interface IDefer { 6 | resolve(): void 7 | } 8 | -------------------------------------------------------------------------------- /test.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:file-name-casing */ 2 | /** 3 | * Created by tushar on 2019-05-06 4 | */ 5 | 6 | export {} 7 | -------------------------------------------------------------------------------- /packages/website/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tusharmath/qio/HEAD/packages/website/static/img/favicon.ico -------------------------------------------------------------------------------- /packages/website/static/img/oss_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tusharmath/qio/HEAD/packages/website/static/img/oss_logo.png -------------------------------------------------------------------------------- /packages/benchmarks/internals/BENCHMARKS.tmpl.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: benchmarks 3 | title: Benchmarks 4 | sidebar_label: Benchmarks 5 | --- 6 | -------------------------------------------------------------------------------- /packages/core/lib/internals/CBExit.ts: -------------------------------------------------------------------------------- 1 | import {Exit} from '../main/Exit' 2 | 3 | export type CBExit = (O: Exit) => void 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | !*.js 4 | *.tgz 5 | test/ 6 | __tests__/ 7 | tsconfig.json 8 | .idea/ 9 | .prettierrc 10 | *.yml 11 | -------------------------------------------------------------------------------- /packages/benchmarks/internals/PrintLn.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line: no-console 2 | export const PrintLn = (...t: unknown[]) => console.log(...t) 3 | -------------------------------------------------------------------------------- /packages/core/__tests__/internals/CreateArray.ts: -------------------------------------------------------------------------------- 1 | export const createArray = (n: number) => [ 2 | ...Array.from({length: n}, (_, i) => i), 3 | ] 4 | -------------------------------------------------------------------------------- /packages/core/lib/internals/CB.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A callback function that takes in one param and returns a void 3 | */ 4 | export type CB = (T: T) => void 5 | -------------------------------------------------------------------------------- /packages/core/lib/internals/IDGenerator.ts: -------------------------------------------------------------------------------- 1 | export class IDGenerator { 2 | private id = 0 3 | public create(): number { 4 | return this.id++ 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/stream/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:file-name-casing */ 2 | /** 3 | * Created by tushar on 31/10/19 4 | */ 5 | 6 | export {QStream} from './lib/QStream' 7 | -------------------------------------------------------------------------------- /packages/prelude/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable: file-name-casing */ 2 | 3 | export * from './Id' 4 | export * from './Noop' 5 | export * from './T' 6 | export * from './Range' 7 | -------------------------------------------------------------------------------- /packages/prelude/Id.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple Id function that takes in an input and returns the same value as it is. 3 | * @ignore 4 | */ 5 | export const Id = (_: A): A => _ 6 | -------------------------------------------------------------------------------- /packages/core/lib/main/Log.ts: -------------------------------------------------------------------------------- 1 | import {QIO} from './QIO' 2 | 3 | // tslint:disable-next-line: no-console 4 | export const log = (...t: unknown[]) => QIO.lift(() => console.log(...t)) 5 | -------------------------------------------------------------------------------- /packages/fs/README.md: -------------------------------------------------------------------------------- 1 | # `@qio/fs` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const fs = require('@qio/fs'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/http/README.md: -------------------------------------------------------------------------------- 1 | # `http` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const http = require('http'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/chai/README.md: -------------------------------------------------------------------------------- 1 | # `@qio/chai` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const chai = require('@qio/chai'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/console/README.md: -------------------------------------------------------------------------------- 1 | # `console` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const console = require('console'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | # `@qio/core` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const core = require('@qio/core'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/docs/README.md: -------------------------------------------------------------------------------- 1 | # `@qio/docs` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const docs = require('@qio/docs'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/stream/README.md: -------------------------------------------------------------------------------- 1 | # `@qio/stream` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const stream = require('@qio/stream'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/example/README.md: -------------------------------------------------------------------------------- 1 | # `@qio/example` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const example = require('@qio/example'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/scripts/README.md: -------------------------------------------------------------------------------- 1 | # `@qio/scripts` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const scripts = require('@qio/scripts'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/benchmarks/README.md: -------------------------------------------------------------------------------- 1 | # `@qio/benchmarks` 2 | 3 | ## Usage 4 | 5 | 1. Change directory to `packages/benchmarks` 6 | 2. Execute `./run.sh` 7 | 3. Output is generated inside `docs/others/benchmarks.md` 8 | -------------------------------------------------------------------------------- /packages/benchmarks/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | DESTINATION="../docs/others/benchmarks.md" 4 | 5 | cp internals/BENCHMARKS.tmpl.md "$DESTINATION" 6 | 7 | ls -d IO/*.js | xargs -L 1 node >> $DESTINATION 8 | -------------------------------------------------------------------------------- /packages/prototype/README.md: -------------------------------------------------------------------------------- 1 | # `@qio/prototype` 2 | 3 | > TODO: description 4 | 5 | ## Usage 6 | 7 | ``` 8 | const prototype = require('@qio/prototype'); 9 | 10 | // TODO: DEMONSTRATE API 11 | ``` 12 | -------------------------------------------------------------------------------- /packages/chai/Chai.ts: -------------------------------------------------------------------------------- 1 | import * as chai from 'chai' 2 | import * as spies from 'chai-spies' 3 | 4 | chai.use(spies) 5 | 6 | const should = chai.should() 7 | const expect = chai.expect 8 | 9 | export {should, expect} 10 | -------------------------------------------------------------------------------- /packages/prelude/Range.ts: -------------------------------------------------------------------------------- 1 | export const range = (s: number, b: number, fn: (i: number) => A): A[] => { 2 | const A = new Array() 3 | for (let i = s; i <= b; i++) { 4 | A.push(fn(i)) 5 | } 6 | 7 | return A 8 | } 9 | -------------------------------------------------------------------------------- /packages/website/blog/2017-09-26-adding-rss.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Adding RSS Support 3 | author: Eric Nakagawa 4 | authorURL: http://twitter.com/ericnakagawa 5 | authorFBID: 661277173 6 | --- 7 | 8 | This is a test post. 9 | 10 | A whole bunch of other information. 11 | -------------------------------------------------------------------------------- /packages/core/__tests__/internals/CreateMockChunks.ts: -------------------------------------------------------------------------------- 1 | import {Chunk} from '../../lib/main/Chunk' 2 | 3 | export const createMockChunks = (n: number, c: number) => { 4 | const array = Array.from({length: n}).map((_, i) => i + 1) 5 | 6 | return Chunk.createN(array, c) 7 | } 8 | -------------------------------------------------------------------------------- /packages/website/blog/2017-10-24-new-version-1.0.0.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: New Version 1.0.0 3 | author: Eric Nakagawa 4 | authorURL: http://twitter.com/ericnakagawa 5 | authorFBID: 661277173 6 | --- 7 | 8 | This blog post will test file name parsing issues when periods are present. 9 | -------------------------------------------------------------------------------- /packages/core/lib/main/Counter.ts: -------------------------------------------------------------------------------- 1 | import {QIO} from '../..' 2 | 3 | export class Counter { 4 | public count = 0 5 | public get increased(): boolean { 6 | return this.count > 0 7 | } 8 | public inc = (s: number = 1) => QIO.lift(() => (this.count += s)) 9 | } 10 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "31.1.1", 6 | "npmClient": "yarn", 7 | "useWorkspaces": true, 8 | "command": { 9 | "version": { 10 | "message": "chore(release): publish `%s` [ci skip]" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/docs/others/credits.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Credits 3 | --- 4 | 5 | QIO is heavily inspired by the following libraries: 6 | 7 | - [Scala ZIO](https://github.com/zio/zio) 8 | - [Fluture](https://github.com/fluture-js/Fluture) 9 | - [BlueBirdJS](https://github.com/petkaantonov/bluebird) 10 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-kips": { 3 | "promptValues": { 4 | "projectName": "qio", 5 | "projectDescription": "A typesafe functional module that solves practical IO problems for node and the browser.", 6 | "keywords": "io functional typescript pure promise" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !Global.d.ts 2 | .idea/ 3 | .nyc_output/ 4 | .vscode/ 5 | *.0x/ 6 | *.d.ts 7 | *.js 8 | *.log 9 | *.map 10 | *.tgz 11 | *.tsbuildinfo 12 | **repl** 13 | coverage/ 14 | node_modules/ 15 | !packages/website/**/*.js 16 | !packages/website/**.js 17 | packages/website/build 18 | packages/website/i18n 19 | packages/docs/api 20 | -------------------------------------------------------------------------------- /packages/docs/fs/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation 3 | --- 4 | 5 | | Version | ![npm](https://img.shields.io/npm/v/@qio/fs.svg) | 6 | | :-----: | -----------------------------------------------: | 7 | 8 | 9 | ## npm 10 | 11 | ```bash 12 | npm i @qio/fs --save 13 | ``` 14 | 15 | ## yarn 16 | 17 | ```bash 18 | yarn add @qio/fs 19 | ``` 20 | -------------------------------------------------------------------------------- /packages/docs/core/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation 3 | --- 4 | 5 | | Version | ![npm](https://img.shields.io/npm/v/@qio/core.svg) | 6 | | :-----: | -------------------------------------------------: | 7 | 8 | 9 | ## npm 10 | 11 | ```bash 12 | npm i @qio/core --save 13 | ``` 14 | 15 | ## yarn 16 | 17 | ```bash 18 | yarn add @qio/core 19 | ``` 20 | -------------------------------------------------------------------------------- /packages/docs/http/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation 3 | --- 4 | 5 | | Version | ![npm](https://img.shields.io/npm/v/@qio/http.svg) | 6 | | :-----: | -------------------------------------------------: | 7 | 8 | 9 | ## npm 10 | 11 | ```bash 12 | npm i @qio/http --save 13 | ``` 14 | 15 | ## yarn 16 | 17 | ```bash 18 | yarn add @qio/http 19 | ``` 20 | -------------------------------------------------------------------------------- /packages/docs/stream/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation 3 | --- 4 | 5 | | Version | ![npm](https://img.shields.io/npm/v/@qio/core.svg) | 6 | | :-----: | -------------------------------------------------: | 7 | 8 | 9 | ## npm 10 | 11 | ```bash 12 | npm i @qio/stream --save 13 | ``` 14 | 15 | ## yarn 16 | 17 | ```bash 18 | yarn add @qio/stream 19 | ``` 20 | -------------------------------------------------------------------------------- /packages/core/lib/internals/OnError.ts: -------------------------------------------------------------------------------- 1 | import {inNode} from 'in-node' 2 | 3 | /** 4 | * Default onError handler. It logs the error on `stderr` stream and exits with a status code 1. 5 | * @ignore 6 | */ 7 | export const onError = (e: E) => { 8 | // tslint:disable-next-line:no-console 9 | console.error(e) 10 | if (inNode) { 11 | process.exit(1) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/docs/console/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation 3 | --- 4 | 5 | | Version | ![npm](https://img.shields.io/npm/v/@qio/console.svg) | 6 | | :-----: | ----------------------------------------------------: | 7 | 8 | 9 | ## npm 10 | 11 | ```bash 12 | npm i @qio/console --save 13 | ``` 14 | 15 | ## yarn 16 | 17 | ```bash 18 | yarn add @qio/console 19 | ``` 20 | -------------------------------------------------------------------------------- /packages/core/lib/main/FS.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tushar on 13/09/19 3 | */ 4 | 5 | import {promises} from 'fs' 6 | 7 | import {QIO} from './QIO' 8 | 9 | export const read = QIO.encaseP(promises.read) 10 | export const readFile = QIO.encaseP(promises.readFile) 11 | export const write = QIO.encaseP(promises.write) 12 | export const writeFile = QIO.encaseP(promises.writeFile) 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "commonjs", 5 | "strict": true, 6 | "noUnusedLocals": true, 7 | "plugins": [ 8 | {"name": "typescript-tslint-plugin", "configFile": "./tslint.json"} 9 | ], 10 | "incremental": true, 11 | "removeComments": true 12 | }, 13 | "exclude": ["packages/type-test"] 14 | } 15 | -------------------------------------------------------------------------------- /packages/core/__tests__/internals/Resource.ts: -------------------------------------------------------------------------------- 1 | import {QIO} from '../../lib/main/QIO' 2 | 3 | export const Resource = (initialCount: number = 0) => { 4 | let i = initialCount 5 | 6 | return { 7 | acquire: QIO.lift(() => ++i), 8 | release: QIO.encase(() => void --i), 9 | get count(): number { 10 | return i 11 | }, 12 | get isReleased(): boolean { 13 | return i === 0 14 | }, 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/core/lib/main/Flag.ts: -------------------------------------------------------------------------------- 1 | import {QIO} from './QIO' 2 | import {Ref} from './Ref' 3 | 4 | export class Flag { 5 | public static of(value: boolean): QIO { 6 | return Ref.of(value).map((awt) => new Flag(awt)) 7 | } 8 | private constructor(private readonly flag: Ref) {} 9 | public get check(): QIO { 10 | return this.flag.read 11 | } 12 | 13 | public set(value: boolean): QIO { 14 | return this.flag.set(value) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | yarn: true 4 | directories: 5 | - ~/.npm 6 | notifications: 7 | email: false 8 | node_js: 9 | - 'stable' 10 | branches: 11 | except: 12 | - /^v\d+\.\d+\.\d+$/ 13 | script: 14 | - yarn lint 15 | - yarn test -R=min 16 | before_install: yarn global add greenkeeper-lockfile@1 17 | before_script: greenkeeper-lockfile-update 18 | after_script: greenkeeper-lockfile-upload 19 | 20 | after_success: 21 | - ./scripts/publish.sh 22 | -------------------------------------------------------------------------------- /packages/benchmarks/IO/Constant.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tushar on 2019-05-09 3 | */ 4 | import * as T from '@matechs/effect' 5 | import {QIO} from '@qio/core' 6 | import {Promise} from 'bluebird' 7 | import * as Fluture from 'fluture' 8 | 9 | import {RunSuite} from '../internals/RunSuite' 10 | 11 | RunSuite('Constant', { 12 | bluebird: () => Promise.resolve(10), 13 | fluture: () => Fluture.resolve(10), 14 | matechs: () => T.effect.pure(10), 15 | qio: () => QIO.resolve(10), 16 | }) 17 | -------------------------------------------------------------------------------- /packages/example/guess-the-number/src/Run.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tushar on 2019-05-05 3 | */ 4 | 5 | /* tslint:disable: no-import-side-effect ordered-imports no-submodule-imports */ 6 | 7 | import 'source-map-support/register' 8 | 9 | import {TTY} from '@qio/console' 10 | import {defaultRuntime} from '@qio/core' 11 | 12 | import {program} from './Program' 13 | 14 | const runtime = defaultRuntime() 15 | runtime.unsafeExecute( 16 | program.provide({ 17 | math: Math, 18 | tty: TTY, 19 | }) 20 | ) 21 | -------------------------------------------------------------------------------- /packages/core/lib/main/Reservation.ts: -------------------------------------------------------------------------------- 1 | import {QIO} from './QIO' 2 | 3 | export class Reservation { 4 | public static of( 5 | acquire: QIO, 6 | release: QIO 7 | ): Reservation { 8 | return new Reservation( 9 | acquire.addEnv(), 10 | release.addEnv() 11 | ) 12 | } 13 | private constructor( 14 | public readonly acquire: QIO, 15 | public readonly release: QIO 16 | ) {} 17 | } 18 | -------------------------------------------------------------------------------- /packages/docs/concepts/pure-functions.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Pure Functions 3 | --- 4 | 5 | In functional programing functions that are **effect-free**, **deterministic** & **total** are called as **pure functions**: 6 | 7 | 1. **Effect Free:** The function never performs any changes to the outside world (no side-effects). 8 | 2. **Deterministic:** Given the same input, the function will always return the same output. 9 | 3. **Total:** The function is defined over the complete set of possible input values & doesn't ever throw exceptions or produce an anomaly. 10 | -------------------------------------------------------------------------------- /packages/example/fibonacci/Fibonacci.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable: no-console strict-comparisons */ 2 | 3 | const memoize = (fn: (a: A) => B) => { 4 | const dict = new Map() 5 | 6 | return (a: A): B => { 7 | if (dict.has(a)) { 8 | return dict.get(a) as B 9 | } 10 | 11 | const b = fn(a) 12 | dict.set(a, b) 13 | 14 | return b 15 | } 16 | } 17 | const fib = memoize((n: bigint): bigint => { 18 | if (n <= 2n) { 19 | return n 20 | } 21 | 22 | return fib(n - 1n) + fib(n - 2n) 23 | }) 24 | 25 | console.log(fib(10000n)) 26 | -------------------------------------------------------------------------------- /packages/docs/concepts/referential-transparency.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Referential Transparency 3 | --- 4 | 5 | Pure functions are referentially transparent. This means in a program the function calls may be replaced by its value (or anything having the same value) without changing the behavior of the program. 6 | 7 | This is a powerful guarantee which allows devs to refactor code much more [easily](side-effects). 8 | 9 | > This is exactly why QIO was built. **QIO** helps write applications using pure functions. It does this by lifting impure functions into pure ones. 10 | -------------------------------------------------------------------------------- /packages/website/blog/2017-09-25-testing-rss.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Adding RSS Support - RSS Truncation Test 3 | author: Eric Nakagawa 4 | authorURL: http://twitter.com/ericnakagawa 5 | authorFBID: 661277173 6 | --- 7 | 8 | 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 9 | 10 | This should be truncated. 11 | 12 | 13 | 14 | This line should never render in XML. 15 | -------------------------------------------------------------------------------- /packages/website/static/css/custom.css: -------------------------------------------------------------------------------- 1 | /* your custom css */ 2 | 3 | @media only screen and (min-device-width: 360px) and (max-device-width: 736px) { 4 | } 5 | 6 | @media only screen and (min-width: 1024px) { 7 | } 8 | 9 | @media only screen and (max-width: 1023px) { 10 | } 11 | 12 | @media only screen and (min-width: 1400px) { 13 | } 14 | 15 | @media only screen and (min-width: 1500px) { 16 | } 17 | 18 | body { 19 | font-family: $defaultFont; 20 | } 21 | 22 | code { 23 | font-family: $monospaceFont; 24 | } 25 | 26 | .fixedHeaderContainer { 27 | box-shadow: 0px 4px 10px 0px #000000; 28 | } 29 | -------------------------------------------------------------------------------- /packages/benchmarks/IO/Map.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tushar on 2019-05-09 3 | */ 4 | import * as T from '@matechs/effect' 5 | import {QIO} from '@qio/core' 6 | import {Promise} from 'bluebird' 7 | import * as Fluture from 'fluture' 8 | 9 | import {inc} from '../internals/Inc' 10 | import {RunSuite} from '../internals/RunSuite' 11 | 12 | RunSuite('Map', { 13 | bluebird: () => Promise.resolve(BigInt(10)).then(inc), 14 | fluture: () => Fluture.map(inc)(Fluture.resolve(BigInt(10))), 15 | matechs: () => T.effect.map(inc)(T.effect.pure(BigInt(10))), 16 | qio: () => QIO.resolve(BigInt(10)).map(inc), 17 | }) 18 | -------------------------------------------------------------------------------- /packages/example/fibonacci/FibonacciIO.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable: no-use-before-declare no-console no-unbound-method strict-comparisons */ 2 | 3 | import {defaultRuntime, FMap, QIO} from '@qio/core' 4 | 5 | const fib = (N: bigint) => 6 | FMap.of().chain((cache) => { 7 | const itar = cache.memoize( 8 | (n: bigint): QIO => { 9 | if (n <= 2n) { 10 | return QIO.resolve(n) 11 | } 12 | 13 | return itar(n - 1n).zipWith(itar(n - 2n), (a, b) => a + b) 14 | } 15 | ) 16 | 17 | return itar(N) 18 | }) 19 | 20 | defaultRuntime().unsafeExecute(fib(10n), console.log) 21 | -------------------------------------------------------------------------------- /packages/prelude/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qio/prelude", 3 | "version": "31.1.0", 4 | "description": "helper utilities for QIO and its sister project", 5 | "author": "Tushar Mathur ", 6 | "homepage": "https://github.com/tusharmath/qio#readme", 7 | "license": "ISC", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/tusharmath/qio.git" 11 | }, 12 | "scripts": { 13 | "test": "echo \"Error: run tests from root\" && exit 1" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/tusharmath/qio/issues" 17 | }, 18 | "publishConfig": { 19 | "access": "public" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/core/lib/main/Snapshot.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tushar on 07/09/19 3 | */ 4 | import {debug} from 'debug' 5 | 6 | import {QIO} from './QIO' 7 | 8 | const D = debug('qio:snapshot') 9 | export class Snapshot { 10 | public readonly timelineData = new Array<[T, number]>() 11 | public get timeline(): string[] { 12 | return this.timelineData.map((_) => _.join('@')) 13 | } 14 | public mark(value: T): QIO { 15 | return QIO.runtime().chain((RTM) => 16 | QIO.lift(() => { 17 | D('mark %O', value) 18 | this.timelineData.push([value, RTM.scheduler.now()]) 19 | }).const(value) 20 | ) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/core/lib/main/Ref.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tushar on 2019-05-25 3 | */ 4 | import {QIO} from './QIO' 5 | 6 | /** 7 | * A pure implementation of a state. 8 | * @typeparam A Type of state to be maintained 9 | */ 10 | export class Ref { 11 | public static of(a: A): QIO> { 12 | return QIO.lift(() => new Ref(a)) 13 | } 14 | private constructor(private value: A) {} 15 | public get read(): QIO { 16 | return QIO.lift(() => this.value) 17 | } 18 | public set(a: A): QIO { 19 | return QIO.lift(() => (this.value = a)) 20 | } 21 | public update(ab: (a: A) => A): QIO { 22 | return QIO.lift(() => (this.value = ab(this.value))) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /scripts/publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Constants 4 | DEPLOY_BRANCH="master" 5 | GH_USER="tusharmath" 6 | GH_PROJECT="qio" 7 | 8 | 9 | # Deploy 10 | if [[ "${TRAVIS_BRANCH}" == ${DEPLOY_BRANCH} ]]; then 11 | git config --global user.email travis@travis-ci.org 12 | git config --global user.name Travis CI 13 | git remote set-url origin "https://${GH_TOKEN}@github.com/${GH_USER}/${GH_PROJECT}.git" > /dev/null 2>&1 14 | git checkout "${DEPLOY_BRANCH}" 15 | echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" >> $HOME/.npmrc 2> /dev/null 16 | lerna publish --conventional-commits --yes 17 | else 18 | echo "Not ${DEPLOY_BRANCH} branch, skipping publishing" 19 | fi -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "excludeExternals": false, 3 | "excludeNotExported": true, 4 | "excludePrivate": true, 5 | "excludeProtected": true, 6 | "hideGenerator": true, 7 | "mode": "file", 8 | "out": "packages/docs/api", 9 | "exclude": ["test/**", "example/**", "benchmarks/**"], 10 | "plugin": ["typedoc-plugin-markdown"], 11 | "listInvalidSymbolLinks": true, 12 | "toc": [ 13 | "QIO", 14 | "UIO", 15 | "IO", 16 | "Task", 17 | "TaskR", 18 | "Ref", 19 | "Fiber", 20 | "Await", 21 | "Exit", 22 | "Managed", 23 | "Queue", 24 | "Stream", 25 | "IRuntime", 26 | "BaseRuntime", 27 | "DefaultRuntime", 28 | "TestRuntime" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /packages/docs/concepts/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | --- 4 | 5 | A _real world_ program needs to deal with **side-effects**, **non determinism** and **anomalies**. Unfortunately most programming languages don't provide us with a first class support to handle any of these problems. **QIO** provides a set of type-safe abstraction that can help solve these problems elegantly using typescript. 6 | 7 | We will try to understand these concepts a bit more. It's important to understand them because throughout the tutorial words like _pure_, _deterministic_, _total_ etc. are going to be used. 8 | 9 | > If you know them already you can directly skip to the [tutorial] directly. 10 | 11 | [tutorial]: ../core/installation 12 | -------------------------------------------------------------------------------- /packages/core/lib/runtimes/FiberRuntime.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tushar on 2019-05-25 3 | */ 4 | import {ICancellable, IScheduler} from 'ts-scheduler' 5 | 6 | import {CBExit} from '../internals/CBExit' 7 | import {Fiber} from '../internals/Fiber' 8 | import {FiberConfig} from '../internals/FiberConfig' 9 | import {QIO} from '../main/QIO' 10 | 11 | import {IRuntime} from './IRuntime' 12 | 13 | export abstract class FiberRuntime implements IRuntime { 14 | public abstract config: FiberConfig 15 | public abstract scheduler: IScheduler 16 | public abstract configure(config: FiberConfig): IRuntime 17 | public unsafeExecute(io: QIO, cb?: CBExit): ICancellable { 18 | return Fiber.unsafeExecuteWith(io, this, cb) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qio/docs", 3 | "private": true, 4 | "version": "31.1.1", 5 | "description": "Documentation files for QIO", 6 | "author": "Tushar Mathur ", 7 | "homepage": "https://github.com/tusharmath/qio#readme", 8 | "license": "ISC", 9 | "main": "lib/docs.js", 10 | "directories": { 11 | "lib": "lib", 12 | "test": "__tests__" 13 | }, 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/tusharmath/qio.git" 20 | }, 21 | "scripts": { 22 | "test": "echo \"Error: run tests from root\" && exit 1" 23 | }, 24 | "bugs": { 25 | "url": "https://github.com/tusharmath/qio/issues" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 4-digit year, Company or Person's Name 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 6 | 7 | Source: http://opensource.org/licenses/ISC 8 | -------------------------------------------------------------------------------- /packages/core/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:file-name-casing */ 2 | export {Await} from './lib/main/Await' 3 | export {Chunk} from './lib/main/Chunk' 4 | export {Counter} from './lib/main/Counter' 5 | export {defaultRuntime} from './lib/runtimes/DefaultRuntime' 6 | export {Exit} from './lib/main/Exit' 7 | export {Fiber} from './lib/internals/Fiber' 8 | export {FiberConfig} from './lib/internals/FiberConfig' 9 | export {Flag} from './lib/main/Flag' 10 | export {FMap} from './lib/main/FMap' 11 | export {IRuntime} from './lib/runtimes/IRuntime' 12 | export {Managed} from './lib/main/Managed' 13 | export {QIO} from './lib/main/QIO' 14 | export {Queue} from './lib/main/Queue' 15 | export {Ref} from './lib/main/Ref' 16 | export {Snapshot} from './lib/main/Snapshot' 17 | export {Stack} from './lib/internals/Stack' 18 | export {testRuntime, TestRuntime} from './lib/runtimes/TestRuntime' -------------------------------------------------------------------------------- /packages/core/__tests__/YieldASAP.test.ts: -------------------------------------------------------------------------------- 1 | import {assert} from 'chai' 2 | import {testScheduler} from 'ts-scheduler' 3 | 4 | import {YieldCount} from '../lib/internals/YieldStrategy' 5 | 6 | describe('YieldASAP', () => { 7 | context('instruction count is Infinite', () => { 8 | it('should set it to MAX_SAFE_INTEGER', () => { 9 | assert.strictEqual( 10 | new YieldCount(testScheduler()).maxCount, 11 | Number.MAX_SAFE_INTEGER 12 | ) 13 | }) 14 | }) 15 | context('instruction count is negative', () => { 16 | it('should set it to 1', () => { 17 | assert.strictEqual(new YieldCount(testScheduler(), -100).maxCount, 1) 18 | }) 19 | }) 20 | context('instruction count is zero', () => { 21 | it('should set it to 1', () => { 22 | assert.strictEqual(new YieldCount(testScheduler(), 0).maxCount, 1) 23 | }) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /packages/prototype/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qio/prototype", 3 | "version": "31.1.1", 4 | "private": true, 5 | "description": "Prototypes for that are based on QIO but are not a part of the core package.", 6 | "author": "Tushar Mathur ", 7 | "homepage": "https://github.com/tusharmath/qio#readme", 8 | "license": "ISC", 9 | "main": "index.js", 10 | "directories": { 11 | "lib": "lib", 12 | "test": "__tests__" 13 | }, 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/tusharmath/qio.git" 20 | }, 21 | "scripts": { 22 | "test": "echo \"Error: run tests from root\" && exit 1" 23 | }, 24 | "bugs": { 25 | "url": "https://github.com/tusharmath/qio/issues" 26 | }, 27 | "dependencies": { 28 | "@qio/core": "^31.1.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/stream/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qio/stream", 3 | "version": "31.1.1", 4 | "description": "Streams based on QIO", 5 | "author": "Tushar Mathur ", 6 | "homepage": "https://github.com/tusharmath/qio#readme", 7 | "license": "ISC", 8 | "main": "index.js", 9 | "publishConfig": { 10 | "access": "public" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/tusharmath/qio.git" 15 | }, 16 | "scripts": { 17 | "test": "echo \"Error: run tests from root\" && exit 1" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/tusharmath/qio/issues" 21 | }, 22 | "dependencies": { 23 | "@qio/core": "^31.1.1", 24 | "@qio/prelude": "^31.1.0", 25 | "debug": "^4.1.1", 26 | "standard-data-structures": "^4.0.0" 27 | }, 28 | "devDependencies": { 29 | "chai": "^4.2.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qio/scripts", 3 | "private": true, 4 | "version": "31.1.1", 5 | "description": "Private scripts for lerna", 6 | "keywords": [ 7 | "qio", 8 | "script" 9 | ], 10 | "author": "Tushar Mathur ", 11 | "homepage": "https://github.com/tusharmath/qio#readme", 12 | "license": "ISC", 13 | "directories": { 14 | "lib": "lib", 15 | "test": "__tests__" 16 | }, 17 | "publishConfig": { 18 | "access": "public" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/tusharmath/qio.git" 23 | }, 24 | "scripts": { 25 | "test": "echo \"Error: run tests from root\" && exit 1" 26 | }, 27 | "bugs": { 28 | "url": "https://github.com/tusharmath/qio/issues" 29 | }, 30 | "dependencies": { 31 | "@qio/console": "^31.1.1", 32 | "@qio/core": "^31.1.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/docs/others/ecosystem.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Ecosystem 3 | --- 4 | 5 | QIO is built with a mono-repo setup.The official packages within it's repository are listed below. 6 | 7 | | Project | Description | 8 | | ---------------------- | ---------------------------------------------------------------- | 9 | | **[@qio/stream]** | Provides streaming capabilities that interop with **@qio/core**. | 10 | | **[@qio/console]** | Provides QIO based bindings for input/output using `stdin` . | 11 | | **[@qio/http]** `BETA` | Provides QIO based binding for `HTTP/HTTPs` requests. | 12 | | **[@qio/fs]** `BETA` | Provides QIO based bindings for node.js `fs` operations. | 13 | 14 | [@qio/stream]: ../stream/installation 15 | [@qio/console]: ../console/installation 16 | [@qio/http]: ../http/installation 17 | [@qio/fs]: ../fs/installation 18 | -------------------------------------------------------------------------------- /packages/core/lib/internals/FiberConfig.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable: prefer-function-over-method switch-default */ 2 | 3 | /** 4 | * Tags used to identify the scheduling strategy. 5 | */ 6 | export enum YieldStrategyTag { 7 | INS_COUNT, 8 | DURATION, 9 | } 10 | 11 | export interface IAsapM { 12 | maxInstructionCount?: number 13 | tag: YieldStrategyTag.INS_COUNT 14 | } 15 | 16 | export interface IDurationM { 17 | maxDuration: number 18 | tag: YieldStrategyTag.DURATION 19 | } 20 | 21 | export type FiberConfig = IAsapM | IDurationM 22 | 23 | export const FiberConfig = { 24 | get DEFAULT(): IAsapM { 25 | return FiberConfig.MAX_INSTRUCTION_COUNT() 26 | }, 27 | MAX_DURATION(maxDuration: number): IDurationM { 28 | return {tag: YieldStrategyTag.DURATION, maxDuration} 29 | }, 30 | MAX_INSTRUCTION_COUNT(maxInstructionCount?: number): IAsapM { 31 | return {tag: YieldStrategyTag.INS_COUNT, maxInstructionCount} 32 | }, 33 | } 34 | -------------------------------------------------------------------------------- /packages/core/lib/internals/Stack.ts: -------------------------------------------------------------------------------- 1 | export class Stack { 2 | private cache: A | undefined = undefined 3 | 4 | private readonly stackA = new Array() 5 | public get top(): A | undefined { 6 | if (this.cache === undefined) { 7 | if (this.stackA.length === 0) { 8 | return undefined 9 | } 10 | 11 | return this.stackA[this.stackA.length - 1] 12 | } 13 | 14 | return this.cache 15 | } 16 | public isEmpty(): boolean { 17 | return this.cache === undefined && this.stackA.length === 0 18 | } 19 | public pop(): A | undefined { 20 | if (this.cache !== undefined) { 21 | const r = this.cache 22 | this.cache = undefined 23 | 24 | return r 25 | } 26 | 27 | return this.stackA.pop() 28 | } 29 | public push(a: A): void { 30 | if (this.cache === undefined) { 31 | this.cache = a 32 | } else { 33 | this.stackA.push(this.cache) 34 | this.cache = a 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/core/lib/runtimes/DefaultRuntime.ts: -------------------------------------------------------------------------------- 1 | import {IScheduler, scheduler as dScheduler} from 'ts-scheduler' 2 | 3 | import {FiberConfig} from '../internals/FiberConfig' 4 | import {Exit} from '../main/Exit' 5 | import {QIO} from '../main/QIO' 6 | 7 | import {FiberRuntime} from './FiberRuntime' 8 | 9 | export class DefaultRuntime extends FiberRuntime { 10 | public constructor( 11 | public readonly scheduler: IScheduler, 12 | public readonly config: FiberConfig 13 | ) { 14 | super() 15 | } 16 | 17 | public configure(config: FiberConfig): DefaultRuntime { 18 | return new DefaultRuntime(this.scheduler, config) 19 | } 20 | 21 | public async unsafeExecutePromise(io: QIO): Promise { 22 | return new Promise((res, rej) => { 23 | this.unsafeExecute(io, (exit) => Exit.fold(exit)(undefined, res, rej)) 24 | }) 25 | } 26 | } 27 | 28 | export const defaultRuntime = () => 29 | new DefaultRuntime(dScheduler, FiberConfig.DEFAULT) 30 | -------------------------------------------------------------------------------- /packages/benchmarks/DS/Stack.ts: -------------------------------------------------------------------------------- 1 | import {Stack} from '@qio/core' 2 | import {Suite} from 'benchmark' 3 | 4 | import {PrintLn} from '../internals/PrintLn' 5 | 6 | const MAX = 1e6 7 | const suite = new Suite() 8 | suite 9 | .add('Array', () => { 10 | const s = new Array() 11 | for (let i = 0; i < MAX; i++) { 12 | s.push(i) 13 | if (i % 2 === 0) { 14 | s.pop() 15 | } 16 | } 17 | }) 18 | .add('Stack', () => { 19 | const s = new Stack() 20 | for (let i = 0; i < MAX; i++) { 21 | s.push(i) 22 | if (i % 2 === 0) { 23 | s.pop() 24 | } 25 | } 26 | }) 27 | 28 | .on('cycle', (event: Event) => { 29 | PrintLn(String(event.target)) 30 | }) 31 | 32 | .on('complete', function (this: Suite): void { 33 | PrintLn( 34 | 'Fastest is ' + 35 | this.filter('fastest') 36 | .map((i: {name: string}) => i.name) 37 | .join('') + 38 | '\n```' 39 | ) 40 | }) 41 | .run() 42 | -------------------------------------------------------------------------------- /packages/console/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qio/console", 3 | "version": "31.1.1", 4 | "description": "Console API using QIO", 5 | "author": "Tushar Mathur ", 6 | "homepage": "https://github.com/tusharmath/qio#readme", 7 | "license": "ISC", 8 | "main": "lib/Console.js", 9 | "directories": { 10 | "lib": "lib", 11 | "test": "__tests__" 12 | }, 13 | "publishConfig": { 14 | "access": "public" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/tusharmath/qio.git" 19 | }, 20 | "keywords": [ 21 | "functional", 22 | "console", 23 | "qio", 24 | "putStrLn", 25 | "getStrLn" 26 | ], 27 | "scripts": { 28 | "test": "echo \"Error: run tests from root\" && exit 1" 29 | }, 30 | "bugs": { 31 | "url": "https://github.com/tusharmath/qio/issues" 32 | }, 33 | "dependencies": { 34 | "@qio/core": "^31.1.1" 35 | }, 36 | "devDependencies": { 37 | "chai": "^4.2.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/docs/core/cancellation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Cancellation 3 | --- 4 | 5 | Executing an QIO returns a cancel callback. Essentially a function that when called, aborts the IO from any further execution and releases all the acquired resources. 6 | 7 | ## Create an QIO 8 | 9 | ```ts 10 | + import {QIO} from '@qio/core' 11 | + 12 | + const delayIO = QIO.timeout('Hello World', 1000) 13 | ``` 14 | 15 | Execute by passing it to `defaultRuntime` 16 | 17 | ```ts 18 | - import {QIO} from '@qio/core' 19 | + import {QIO, defaultRuntime} from '@qio/core' 20 | 21 | const delayIO = QIO.timeout('Hello World', 1000) 22 | + const cancel = defaultRuntime().unsafeExecute(delayIO) 23 | ``` 24 | 25 | Calling the cancelling callback. 26 | 27 | ```ts-diff 28 | import {QIO, defaultRuntime} from '@qio/core' 29 | const delayIO = QIO.timeout('Hello World', 1000) 30 | const cancel = defaultRuntime().execute(delayIO) 31 | 32 | + cancel() 33 | ``` 34 | 35 | As soon as `cancel` is called internally the timeout is cancelled. 36 | -------------------------------------------------------------------------------- /packages/docs/core/type-signature.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Type Signature 3 | --- 4 | 5 | ```ts 6 | interface QIO { 7 | // ... Operators 8 | } 9 | ``` 10 | 11 | `QIO` takes in three type params viz. — 12 | 13 | 1. `A` The type of the success value that will be emitted by the IO on completion. 14 | 2. `E` The error types that can be emitted while this IO is executing. 15 | 3. `R` Represents the type of environment needed to execute this IO. 16 | 17 | Using these three type params you can fairly represent any side-effect. For example lets say there is function `Greet` which simply prints "Hello World" — 18 | 19 | ```ts 20 | const Greet = () => console.log('Hello World!') 21 | ``` 22 | 23 | To represent `Greet` — 24 | 25 | 1. `A` could be `void`: The output of running the program is empty. 26 | 2. `E` could be `never`: Printing anything on console never fails. 27 | 3. `R` could be `unknown`: since `console.log` works everywhere. 28 | 29 | ```ts 30 | const GreetIO = Greet() // QIO 31 | ``` 32 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Tushar Mathur ", 3 | "description": "The core QIO API to manage effects", 4 | "keywords": [ 5 | "io", 6 | "functional", 7 | "typescript", 8 | "pure", 9 | "promise" 10 | ], 11 | "license": "ISC", 12 | "main": "index.js", 13 | "name": "@qio/core", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/tusharmath/qio.git" 17 | }, 18 | "version": "31.1.1", 19 | "dependencies": { 20 | "@qio/prelude": "^31.1.0", 21 | "debug": "^4.1.1", 22 | "in-node": "^1.0.0", 23 | "standard-data-structures": "^4.0.0", 24 | "ts-scheduler": "^8.0.4" 25 | }, 26 | "devDependencies": { 27 | "@types/chai": "^4.2.11", 28 | "@types/chai-spies": "^1.0.1", 29 | "@types/debug": "^4.1.5", 30 | "@types/mocha": "^8.0.3", 31 | "chai": "^4.2.0", 32 | "chai-spies": "^1.0.0", 33 | "mocha": "^8.2.0" 34 | }, 35 | "publishConfig": { 36 | "access": "public" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/chai/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qio/chai", 3 | "version": "31.1.1", 4 | "private": true, 5 | "description": "Chai setup for QIO", 6 | "author": "Tushar Mathur ", 7 | "homepage": "https://github.com/tusharmath/qio#readme", 8 | "license": "ISC", 9 | "main": "Chai.js", 10 | "directories": { 11 | "lib": "lib", 12 | "test": "__tests__" 13 | }, 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/tusharmath/qio.git" 20 | }, 21 | "scripts": { 22 | "test": "echo \"Error: run tests from root\" && exit 1" 23 | }, 24 | "bugs": { 25 | "url": "https://github.com/tusharmath/qio/issues" 26 | }, 27 | "devDependencies": { 28 | "@types/chai": "^4.2.11", 29 | "@types/chai-spies": "^1.0.1", 30 | "chai": "^4.2.0", 31 | "chai-spies": "^1.0.0" 32 | }, 33 | "dependencies": { 34 | "@qio/core": "^31.1.1", 35 | "@qio/prelude": "^31.1.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/core/lib/runtimes/IRuntime.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tushar on 2019-05-07 3 | */ 4 | import {ICancellable, IScheduler} from 'ts-scheduler' 5 | 6 | import {CBExit} from '../internals/CBExit' 7 | import {FiberConfig} from '../internals/FiberConfig' 8 | import {QIO} from '../main/QIO' 9 | 10 | /** 11 | * Base runtime that is used to execute any [[QIO]]. 12 | * 13 | * Runtime internally manages scheduling of jobs and their prioritization. 14 | * Depends on [ts-scheduler](https://github.com/tusharmath/ts-scheduler) of internal job scheduling. 15 | * Actual implementation is available at [[DefaultRuntime]] & [[TestRuntime]]. 16 | */ 17 | export interface IRuntime { 18 | readonly config: FiberConfig 19 | readonly scheduler: IScheduler 20 | configure(config: FiberConfig): IRuntime 21 | /** 22 | * Executes the provided [[QIO]] expression. 23 | * Returns a `ICancellable` that can be used to interrupt the execution. 24 | */ 25 | unsafeExecute(io: QIO, cb?: CBExit): ICancellable 26 | } 27 | -------------------------------------------------------------------------------- /packages/website/sidebars.json: -------------------------------------------------------------------------------- 1 | { 2 | "concepts": {}, 3 | "core": { 4 | "Basic": [ 5 | "core/installation", 6 | "core/usage", 7 | "core/effects", 8 | "core/concurrency", 9 | "core/exceptions-anomalies", 10 | "core/cancellation", 11 | "core/environment", 12 | "core/unit-testing" 13 | ], 14 | "Advanced": [ 15 | "core/stack-safety", 16 | "core/fiber", 17 | "core/multi-task", 18 | "core/brackets" 19 | ] 20 | }, 21 | "comparison": { 22 | "Comparison": ["comparison/features", "comparison/benchmarks"] 23 | }, 24 | "entities": { 25 | "Core": [ 26 | "core/qio", 27 | "core/managed", 28 | "core/runtime", 29 | "core/ref", 30 | "core/await", 31 | "core/queue" 32 | ], 33 | "Stream": ["stream/installation", "stream/stream"], 34 | "Console": ["console/installation", "console/usage"], 35 | "Http": ["http/installation", "http/usage"], 36 | "FS": ["fs/installation", "fs/usage"] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/docs/others/benchmarks.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: benchmarks 3 | title: Benchmarks 4 | sidebar_label: Benchmarks 5 | --- 6 | ## Constant 7 | ``` 8 | QIO x 1,942,269 ops/sec ±4.45% (68 runs sampled) 9 | Fluture x 152,359 ops/sec ±8.25% (50 runs sampled) 10 | bluebird x 4,316,323 ops/sec ±2.13% (76 runs sampled) 11 | Fastest is bluebird 12 | ``` 13 | ## CreateNestedMap 1000 14 | ``` 15 | QIO x 15,349 ops/sec ±3.05% (76 runs sampled) 16 | ## Fibonacci: 20 17 | ``` 18 | QIO x 517 ops/sec ±1.83% (75 runs sampled) 19 | Fluture x 129 ops/sec ±3.57% (71 runs sampled) 20 | bluebird x 169 ops/sec ±2.55% (73 runs sampled) 21 | Fastest is QIO 22 | ``` 23 | ## Map 24 | ``` 25 | QIO x 1,405,298 ops/sec ±5.60% (71 runs sampled) 26 | Fluture x 72,885 ops/sec ±12.86% (55 runs sampled) 27 | bluebird x 2,084,296 ops/sec ±2.50% (70 runs sampled) 28 | Fastest is bluebird 29 | ``` 30 | ## NestedChain 10000 31 | ``` 32 | Matechs x 963 ops/sec ±1.58% (79 runs sampled) 33 | QIO x 1,629 ops/sec ±1.89% (73 runs sampled) 34 | Fluture x 186 ops/sec ±2.12% (73 runs sampled) 35 | -------------------------------------------------------------------------------- /packages/docs/core/comparison.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Feature Comparison 3 | --- 4 | 5 | QIO has an edge over other standard effect libraries. 6 | 7 | | Feature | Promise | RxJS | Fluture | QIO | 8 | | ----------------------- | :-----: | :--: | :-----: | :-: | 9 | | Async Effects | ✔ | ✔ | ✔ | ✔ | 10 | | Sync Effects | | ✔ | ✔ | ✔ | 11 | | Precise Parallelism | | ✔ | ✔ | ✔ | 12 | | Lazy Evaluation | | ✔ | ✔ | ✔ | 13 | | Cancellation | | ✔ | ✔ | ✔ | 14 | | Batteries Included | | ✔ | ✔ | ✔ | 15 | | Pure | | | ✔ | ✔ | 16 | | Typed Exceptions | | | ✔ | ✔ | 17 | | Resource Safety | | | ✔ | ✔ | 18 | | Back Pressure | | | | ✔ | 19 | | Dependency Injection | | | | ✔ | 20 | | Fiber Based Concurrency | | | | ✔ | 21 | -------------------------------------------------------------------------------- /packages/http/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qio/http", 3 | "version": "31.1.1", 4 | "description": "QIO bindings for HTTP", 5 | "keywords": [ 6 | "http", 7 | "qio", 8 | "functional", 9 | "programming", 10 | "pure", 11 | "immutable" 12 | ], 13 | "author": "Tushar Mathur ", 14 | "homepage": "http://qio.netlify.com", 15 | "license": "ISC", 16 | "main": "lib/Http.js", 17 | "directories": { 18 | "lib": "lib", 19 | "test": "__tests__" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/tusharmath/qio.git" 24 | }, 25 | "scripts": { 26 | "test": "echo \"Error: run tests from root\" && exit 1" 27 | }, 28 | "bugs": { 29 | "url": "https://github.com/tusharmath/qio/issues" 30 | }, 31 | "dependencies": { 32 | "@qio/core": "^31.1.1", 33 | "axios": "^0.20.0" 34 | }, 35 | "publishConfig": { 36 | "access": "public" 37 | }, 38 | "devDependencies": { 39 | "@types/chai": "^4.2.11", 40 | "chai": "^4.2.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/core/lib/internals/CancellationList.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tushar on 2019-05-24 3 | */ 4 | import { 5 | DoublyLinkedList, 6 | LinkedListNode, 7 | Option, 8 | } from 'standard-data-structures' 9 | import {ICancellable} from 'ts-scheduler' 10 | 11 | /** 12 | * @ignore 13 | */ 14 | 15 | export type CancelId = LinkedListNode 16 | 17 | /** 18 | * @ignore 19 | */ 20 | export class CancellationList implements ICancellable { 21 | private cancelled = false 22 | private readonly Q = DoublyLinkedList.of() 23 | 24 | public get isCancelled(): boolean { 25 | return this.cancelled 26 | } 27 | 28 | public cancel(): void { 29 | this.cancelled = true 30 | while (this.Q.length > 0) { 31 | const node = this.Q.pop() 32 | 33 | if (Option.isSome(node)) { 34 | node.value.cancel() 35 | } 36 | } 37 | } 38 | 39 | public push(cancellable: ICancellable): CancelId { 40 | return this.Q.add(cancellable) 41 | } 42 | 43 | public remove(id: CancelId): void { 44 | this.Q.remove(id) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "@qio/website", 4 | "version": "31.1.0", 5 | "scripts": { 6 | "examples": "docusaurus-examples", 7 | "start": "docusaurus-start", 8 | "build": "docusaurus-build", 9 | "publish-gh-pages": "docusaurus-publish", 10 | "write-translations": "docusaurus-write-translations", 11 | "version:ignore": "docusaurus-version", 12 | "rename-version": "docusaurus-rename-version" 13 | }, 14 | "description": "> TODO: description", 15 | "author": "Tushar Mathur ", 16 | "homepage": "https://github.com/tusharmath/qio#readme", 17 | "license": "ISC", 18 | "main": "lib/website.js", 19 | "directories": { 20 | "lib": "lib", 21 | "test": "__tests__" 22 | }, 23 | "publishConfig": { 24 | "access": "public" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/tusharmath/qio.git" 29 | }, 30 | "bugs": { 31 | "url": "https://github.com/tusharmath/qio/issues" 32 | }, 33 | "dependencies": { 34 | "docusaurus": "^1.14.4" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/fs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qio/fs", 3 | "version": "31.1.1", 4 | "description": "QIO based bindings for interacting with the file system", 5 | "keywords": [ 6 | "fs", 7 | "pure", 8 | "immutable", 9 | "functional", 10 | "programming", 11 | "qio" 12 | ], 13 | "author": "Tushar Mathur ", 14 | "homepage": "https://github.com/tusharmath/qio#readme", 15 | "license": "ISC", 16 | "main": "lib/FS.js", 17 | "directories": { 18 | "lib": "lib", 19 | "test": "__tests__" 20 | }, 21 | "publishConfig": { 22 | "access": "public" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/tusharmath/qio.git" 27 | }, 28 | "scripts": { 29 | "test": "echo \"Error: run tests from root\" && exit 1" 30 | }, 31 | "bugs": { 32 | "url": "https://github.com/tusharmath/qio/issues" 33 | }, 34 | "devDependencies": { 35 | "@types/chai": "^4.2.11", 36 | "@types/mocha": "^8.0.3", 37 | "@types/node": "^14.11.10" 38 | }, 39 | "dependencies": { 40 | "@qio/core": "^31.1.1", 41 | "chai": "^4.2.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qio/example", 3 | "private": true, 4 | "version": "31.1.1", 5 | "description": "> TODO: description", 6 | "author": "Tushar Mathur ", 7 | "homepage": "https://github.com/tusharmath/qio#readme", 8 | "license": "ISC", 9 | "main": "lib/example.js", 10 | "directories": { 11 | "lib": "lib", 12 | "test": "__tests__" 13 | }, 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/tusharmath/qio.git" 20 | }, 21 | "scripts": { 22 | "test": "echo \"Error: run tests from root\" && exit 1" 23 | }, 24 | "bugs": { 25 | "url": "https://github.com/tusharmath/qio/issues" 26 | }, 27 | "devDependencies": { 28 | "@types/chai": "^4.2.11", 29 | "@types/chai-spies": "^1.0.1", 30 | "chai": "^4.2.0", 31 | "chai-spies": "^1.0.0", 32 | "source-map-support": "^0.5.16" 33 | }, 34 | "dependencies": { 35 | "@qio/console": "^31.1.1", 36 | "@qio/core": "^31.1.1", 37 | "@qio/prelude": "^31.1.0", 38 | "@qio/stream": "^31.1.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/docs/comparison/features.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Features 3 | --- 4 | 5 | QIO has an edge over other standard effect libraries. 6 | 7 | | Feature | Promise | RxJS | Fluture | QIO | 8 | | -------------------------------- | :-----: | :--: | :-----: | :-: | 9 | | Manage asynchronous side-effects | ✔ | ✔ | ✔ | ✔ | 10 | | Manager synchronous side-effects | | ✔ | ✔ | ✔ | 11 | | Control parallelism precisely | | ✔ | ✔ | ✔ | 12 | | Cancellation of side-effects | | ✔ | ✔ | ✔ | 13 | | Highly Composable | | ✔ | ✔ | ✔ | 14 | | Purely Functional | | | ✔ | ✔ | 15 | | Typed Exceptions | | | ✔ | ✔ | 16 | | Exception composition | | | ✔ | ✔ | 17 | | Resource Safety | | | ✔ | ✔ | 18 | | Inter-operate with Streams | | | | ✔ | 19 | | Support for dependency injection | | | | ✔ | 20 | | Cooperative yielding of tasks | | | | ✔ | 21 | 22 | -------------------------------------------------------------------------------- /packages/benchmarks/IO/NestedMap.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tushar on 2019-05-11 3 | */ 4 | 5 | import * as T from '@matechs/effect' 6 | import {QIO} from '@qio/core' 7 | import {Promise} from 'bluebird' 8 | import * as Fluture from 'fluture' 9 | 10 | import {inc} from '../internals/Inc' 11 | import {RunSuite} from '../internals/RunSuite' 12 | 13 | const MAX = 1e3 14 | 15 | RunSuite(`NestedMap ${MAX}`, { 16 | bluebird: () => { 17 | let bird = Promise.resolve(BigInt(0)) 18 | for (let i = 0; i < MAX; i++) { 19 | bird = bird.then(inc) 20 | } 21 | 22 | return bird 23 | }, 24 | fluture: () => { 25 | let fluture = Fluture.resolve(BigInt(0)) 26 | for (let i = 0; i < MAX; i++) { 27 | fluture = Fluture.map(inc)(fluture) 28 | } 29 | 30 | return fluture 31 | }, 32 | matechs: () => { 33 | let io = T.effect.pure(0n) 34 | for (let i = 0; i < MAX; i++) { 35 | io = T.effect.map(inc)(io) 36 | } 37 | 38 | return io 39 | }, 40 | qio: () => { 41 | let qio = QIO.resolve(BigInt(0)) 42 | for (let i = 0; i < MAX; i++) { 43 | qio = qio.map(inc) 44 | } 45 | 46 | return qio 47 | }, 48 | }) 49 | -------------------------------------------------------------------------------- /packages/benchmarks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@qio/benchmarks", 3 | "private": true, 4 | "version": "31.1.1", 5 | "description": "> TODO: description", 6 | "author": "Tushar Mathur ", 7 | "homepage": "https://github.com/tusharmath/qio#readme", 8 | "license": "ISC", 9 | "main": "lib/benchmarks.js", 10 | "directories": { 11 | "lib": "lib", 12 | "test": "__tests__" 13 | }, 14 | "publishConfig": { 15 | "access": "public" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/tusharmath/qio.git" 20 | }, 21 | "scripts": { 22 | "test:io": "./run.sh IO ../docs/others/benchmarks.md" 23 | }, 24 | "bugs": { 25 | "url": "https://github.com/tusharmath/qio/issues" 26 | }, 27 | "devDependencies": { 28 | "@types/benchmark": "^1.0.31", 29 | "@types/bluebird": "^3.5.30", 30 | "benchmark": "^2.1.4", 31 | "bluebird": "^3.7.2", 32 | "fluture": "^12.2.0" 33 | }, 34 | "dependencies": { 35 | "@matechs/effect": "^5.0.9", 36 | "@qio/core": "^31.1.1", 37 | "@qio/prelude": "^31.1.0", 38 | "fp-ts": "^2.5.3", 39 | "retry-ts": "^0.1.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/http/__tests__/Http.test.ts: -------------------------------------------------------------------------------- 1 | import {QIO, testRuntime} from '@qio/core' 2 | import {assert} from 'chai' 3 | 4 | import {AxiosResponse, httpEnv, request} from '../lib/Http' 5 | 6 | describe('http', () => { 7 | context('mock', () => { 8 | it('should return a response', () => { 9 | const TEST_RESPONSE = { 10 | data: 'HELLO WORLD', 11 | headers: {}, 12 | status: 200, 13 | statusText: 'ok', 14 | } 15 | const config = {url: 'www.abc.com'} 16 | const actual = testRuntime().unsafeExecuteSync( 17 | request(config).provide({ 18 | http: { 19 | request: (config0) => 20 | QIO.resolve({...TEST_RESPONSE, config: config0}), 21 | }, 22 | }) 23 | ) 24 | const expected = { 25 | ...TEST_RESPONSE, 26 | config, 27 | } 28 | 29 | assert.deepStrictEqual(actual, expected) 30 | }) 31 | }) 32 | context('httpEnv', () => { 33 | it('should be passable', () => { 34 | const config = {url: 'www.abc.com'} 35 | testRuntime().unsafeExecute(request(config).provide({http: httpEnv})) 36 | }) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /packages/core/lib/main/Exit.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable: switch-default */ 2 | export enum FiberExitTag { 3 | SUCCESS, 4 | FAILURE, 5 | CANCELLED, 6 | } 7 | 8 | class FiberCancelled { 9 | public readonly tag = FiberExitTag.CANCELLED 10 | } 11 | 12 | class FiberFailure { 13 | public readonly tag = FiberExitTag.FAILURE 14 | public constructor(public readonly cause: E) {} 15 | } 16 | 17 | class FiberSuccess { 18 | public readonly tag = FiberExitTag.SUCCESS 19 | public constructor(public readonly value: A) {} 20 | } 21 | 22 | export type Exit = FiberCancelled | FiberFailure | FiberSuccess 23 | 24 | export const Exit = { 25 | ...FiberExitTag, 26 | cancel: () => new FiberCancelled(), 27 | fail: (cause: E) => new FiberFailure(cause), 28 | fold: (exit: Exit) => ( 29 | S: S, 30 | AA: (A: A) => S, 31 | EE: (E: E) => S 32 | ): S => { 33 | switch (exit.tag) { 34 | case FiberExitTag.CANCELLED: 35 | return S 36 | case FiberExitTag.FAILURE: 37 | return EE(exit.cause) 38 | case FiberExitTag.SUCCESS: 39 | return AA(exit.value) 40 | } 41 | }, 42 | succeed: (value: A) => new FiberSuccess(value), 43 | } 44 | -------------------------------------------------------------------------------- /packages/core/lib/main/FMap.ts: -------------------------------------------------------------------------------- 1 | import {QIO} from './QIO' 2 | 3 | export class NoSuchElement extends Error {} 4 | 5 | /** 6 | * A light weight wrapper over the Javascript's mutable Map. 7 | * For most use cases a more obvious choice would be to use an [immutable Map]. 8 | * 9 | * [immutable Map]: https://immutable-js.github.io/immutable-js/docs/#/Map 10 | */ 11 | export class FMap { 12 | public static of(): QIO> { 13 | return QIO.lift(() => new FMap()) 14 | } 15 | private readonly cache = new Map() 16 | private constructor() {} 17 | public get(key: K): QIO { 18 | return QIO.lift(() => this.cache.get(key)).chain((_) => 19 | _ === undefined ? QIO.reject(new NoSuchElement()) : QIO.resolve(_) 20 | ) 21 | } 22 | public has(key: K): QIO { 23 | return QIO.lift(() => this.cache.has(key)) 24 | } 25 | public memoize( 26 | fn: (a: K) => QIO 27 | ): (a: K) => QIO { 28 | return (a: K) => this.get(a).catch(() => fn(a).chain((r) => this.set(a, r))) 29 | } 30 | public set(key: K, value: V): QIO { 31 | return QIO.lift(() => void this.cache.set(key, value)).const(value) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": ["tslint:all", "tslint-config-prettier"], 4 | "rules": { 5 | "comment-format": false, 6 | "completed-docs": false, 7 | "interface-name": true, 8 | "no-empty": false, 9 | "no-implicit-dependencies": [true, "dev"], 10 | "no-magic-numbers": false, 11 | "no-parameter-properties": false, 12 | "no-submodule-imports": true, 13 | "no-use-before-declare": false, 14 | "prefer-template": false, 15 | "no-unbound-method": { 16 | "options": "ignore-static" 17 | }, 18 | "no-void-expression": false, 19 | "variable-name": [true, "allow-pascal-case"], 20 | // TEMPORARY SETTINGS 21 | "file-name-casing": [true, "pascal-case"], 22 | "typedef": [true, "call-signature", "parameter"], 23 | "prefer-for-of": false, 24 | "increment-decrement": false, 25 | "max-classes-per-file": false, 26 | "restrict-plus-operands": false, 27 | "invalid-void": false, 28 | // Some tslint bug that's converting numbers to string 29 | "strict-string-expressions": false, 30 | "strict-comparisons": [ 31 | true, 32 | { 33 | "allow-object-equal-comparison": true 34 | } 35 | ] 36 | }, 37 | "rulesDirectory": [] 38 | } 39 | -------------------------------------------------------------------------------- /packages/core/__tests__/Ref.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tushar on 2019-05-25 3 | */ 4 | import {assert} from 'chai' 5 | 6 | import {Ref} from '../lib/main/Ref' 7 | import {testRuntime} from '../lib/runtimes/TestRuntime' 8 | 9 | describe('Ref', () => { 10 | context('update', () => { 11 | it('should update the value', () => { 12 | const actual = testRuntime().unsafeExecuteSync( 13 | Ref.of(1000).chain((_) => _.update((i) => i + 1)) 14 | ) 15 | const expected = 1001 16 | 17 | assert.strictEqual(actual, expected) 18 | }) 19 | }) 20 | 21 | context('read', () => { 22 | it('should read the latest value', () => { 23 | const runtime = testRuntime() 24 | const count = Ref.of(1000) 25 | const actual = runtime.unsafeExecuteSync( 26 | count.chain((_) => _.update((i) => i + 1).and(_.read)) 27 | ) 28 | const expected = 1001 29 | 30 | assert.strictEqual(actual, expected) 31 | }) 32 | }) 33 | 34 | context('set', () => { 35 | it('should set the value', () => { 36 | const runtime = testRuntime() 37 | const count = Ref.of(-1) 38 | const actual = runtime.unsafeExecuteSync(count.chain((_) => _.set(1000))) 39 | const expected = 1000 40 | 41 | assert.strictEqual(actual, expected) 42 | }) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /packages/docs/core/brackets.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Brackets 3 | --- 4 | 5 | Say you want to open a file, write something to it and then close the file: 6 | 7 | ```ts 8 | import {promises as fs} from 'fs' 9 | 10 | const program = async () => { 11 | // Run inside a try-catch block 12 | try { 13 | const fd = await fs.open('./data', 'r+') // Open the file 14 | await fd.write('Hello World!') // Write something to it 15 | } catch (e) { 16 | console.error(e) // Log error 17 | } finally { 18 | await fd.close() // Close the file 19 | } 20 | } 21 | ``` 22 | 23 | The above implementation can be abstracted out using the `bracket` API inside QIO as follows: 24 | 25 | ```ts 26 | import {QIO} from '@qio/core' 27 | import {promises as fs} from 'fs' 28 | 29 | // Open file effect 30 | const open = QIO.encaseP(fs.open) 31 | 32 | // Close file effect 33 | const close = QIO.encaseP((fd: fs.FileHandle) => fd.close()) 34 | 35 | // Create a bracket (try-catch alternative) 36 | const bracket = open('./abc.txt', 'w').bracket(close) 37 | 38 | // Use the bracket 39 | const program = bracket(fd => fd.write('./data')) 40 | ``` 41 | 42 | Brackets will automatically release the resource once it done writing the file. It will also release the file handle on any form of interruption such as an external cancellation or an internal exception. 43 | -------------------------------------------------------------------------------- /packages/docs/core/exceptions-anomalies.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Exception Type 3 | --- 4 | 5 | Consider a simple deterministic function such as `division` that takes in two numbers and returns the result by dividing one over the other: 6 | 7 | ```ts 8 | const division = (a: number, b: number): number => { 9 | return a / b 10 | } 11 | ``` 12 | 13 | The function `division` is defined over a limited set of values for `b`. Passing `b` as `0` will return `Infinity` which is not a `number` thus producing an anomaly in the program at runtime. 14 | 15 | ## Partial Functions 16 | 17 | In functional programming, such functions that are not defined over the complete range of input values, are called as **partial functions**. 18 | 19 | ## Total Functions 20 | 21 | Functions that are defined over the complete range of input values are called as **total functions**. 22 | 23 | ## Enriching Return Type 24 | 25 | Using QIO we can represent the function more clearly: 26 | 27 | ```ts 28 | class DivisionByZero extends Error {} 29 | 30 | const division = (a: number, b: number): QIO => { 31 | return b === 0 ? QIO.reject(new DivisionByZero()) : QIO.resolve(a / b) 32 | } 33 | ``` 34 | 35 | `QIO` is a much better representation than just `number`, because it clearly represents how it can succeed and how it can fail. 36 | -------------------------------------------------------------------------------- /packages/core/lib/runtimes/TestRuntime.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tushar on 2019-05-24 3 | */ 4 | 5 | import {Id} from '@qio/prelude' 6 | import {TestScheduler, testScheduler} from 'ts-scheduler' 7 | 8 | import {FiberConfig} from '../internals/FiberConfig' 9 | import {Exit} from '../main/Exit' 10 | import {QIO} from '../main/QIO' 11 | 12 | import {FiberRuntime} from './FiberRuntime' 13 | 14 | export class TestRuntime extends FiberRuntime { 15 | public constructor( 16 | public readonly scheduler: TestScheduler, 17 | public readonly config: FiberConfig 18 | ) { 19 | super() 20 | } 21 | public configure(config: FiberConfig): TestRuntime { 22 | return new TestRuntime(this.scheduler, config) 23 | } 24 | public unsafeExecuteSync(io: QIO): A | E | undefined { 25 | const exit = this.unsafeExecuteSync0(io) 26 | 27 | return exit !== undefined 28 | ? Exit.fold(exit)(undefined, Id, Id) 29 | : undefined 30 | } 31 | public unsafeExecuteSync0(io: QIO): Exit | undefined { 32 | let result: undefined | Exit 33 | this.unsafeExecute(io, (_) => (result = _)) 34 | this.scheduler.run() 35 | 36 | return result 37 | } 38 | } 39 | 40 | export const testRuntime = (scheduler: TestScheduler = testScheduler()) => 41 | new TestRuntime(scheduler, FiberConfig.DEFAULT) 42 | -------------------------------------------------------------------------------- /packages/core/lib/internals/PureMutableList.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DoublyLinkedList, 3 | LinkedListNode, 4 | Option, 5 | } from 'standard-data-structures' 6 | 7 | import {QIO} from '../main/QIO' 8 | 9 | /** 10 | * A pure version of a mutable doubly linked list 11 | */ 12 | export class PureMutableList { 13 | public static of(): QIO> { 14 | return QIO.lift(() => new PureMutableList()) 15 | } 16 | private readonly list = DoublyLinkedList.of() 17 | private constructor() {} 18 | public get asArray(): QIO { 19 | return QIO.lift(() => this.list.asArray) 20 | } 21 | public get isEmpty(): QIO { 22 | return QIO.lift(() => this.list.isEmpty) 23 | } 24 | public get length(): QIO { 25 | return QIO.lift(() => this.list.length) 26 | } 27 | public get shift(): QIO> { 28 | return QIO.lift(() => this.list.shift()) 29 | } 30 | public add(element: A): QIO> { 31 | return QIO.lift(() => this.list.add(element)) 32 | } 33 | public forEach(f: (a: A) => QIO): QIO { 34 | const itar = (): QIO => 35 | this.shift.chain((_) => _.map(f).getOrElse(QIO.void()).chain(itar)) 36 | 37 | return itar() 38 | } 39 | public remove(node: LinkedListNode): QIO { 40 | return QIO.lift(() => this.list.remove(node)) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/benchmarks/IO/NestedChain.ts: -------------------------------------------------------------------------------- 1 | import * as T from '@matechs/effect' 2 | import {QIO} from '@qio/core' 3 | import {Promise} from 'bluebird' 4 | import * as Fluture from 'fluture' 5 | 6 | import {RunSuite} from '../internals/RunSuite' 7 | 8 | const MAX = 1e4 9 | 10 | const flutureMapper = (_: bigint) => Fluture.resolve(_ + BigInt(1)) 11 | const bluebirdMapper = (_: bigint) => Promise.resolve(_ + BigInt(1)) 12 | const qioMapper = (_: bigint) => QIO.resolve(_ + BigInt(1)) 13 | const matechsMapper = (_: bigint) => T.effect.pure(_ + BigInt(1)) 14 | 15 | RunSuite(`NestedChain ${MAX}`, { 16 | bluebird: () => { 17 | let bird = Promise.resolve(BigInt(0)) 18 | for (let i = 0; i < MAX; i++) { 19 | bird = bird.then(bluebirdMapper) 20 | } 21 | 22 | return bird 23 | }, 24 | fluture: () => { 25 | let fluture = Fluture.resolve(BigInt(0)) 26 | for (let i = 0; i < MAX; i++) { 27 | fluture = Fluture.chain(flutureMapper)(fluture) 28 | } 29 | 30 | return fluture 31 | }, 32 | matechs: () => { 33 | let io = T.effect.pure(0n) 34 | for (let i = 0; i < MAX; i++) { 35 | io = T.effect.chain(matechsMapper)(io) 36 | } 37 | 38 | return io 39 | }, 40 | qio: () => { 41 | let qio = QIO.resolve(BigInt(0)) 42 | for (let i = 0; i < MAX; i++) { 43 | qio = qio.chain(qioMapper) 44 | } 45 | 46 | return qio 47 | }, 48 | }) 49 | -------------------------------------------------------------------------------- /packages/docs/core/managed.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Managed 3 | --- 4 | 5 | Managed is a special data structure that is built to provide certain guarantees around resources. It makes sure that, 6 | 7 | 1. The resource is **acquired** only when an operation is ready to be performed. 8 | 2. The resource is **released** when the operation is completed **successfully**. 9 | 3. The resource is **released** when there is a **failure**. 10 | 4. The resource is **released** when an operation is **cancelled**. 11 | 12 | ## Creating Managed 13 | 14 | You can create a managed resource using `Managed.make`. 15 | 16 | ```diff 17 | + import {Managed, QIO} from '@qio/core' 18 | + import {promises as fs} from 'fs' 19 | + 20 | + const open = QIO.encaseP(fs.open) 21 | + const close = QIO.encaseP((fd: fs.FileHandle) => fd.close()) 22 | + 23 | + const managedFD = Managed.make(open('./abc.txt', 'w'), close) 24 | ``` 25 | 26 | ## Using Managed 27 | 28 | `managedFD` can be used through the `.use` operator. 29 | 30 | ```diff 31 | import {Managed, QIO} from '@qio/core' 32 | import {promises as fs} from 'fs' 33 | 34 | const open = QIO.encaseP(fs.open) 35 | const close = QIO.encaseP((fd: fs.FileHandle) => fd.close()) 36 | const write = QIO.encaseP((fd: fs.FileHandle, buffer: Buffer) => 37 | fd.write(buffer) 38 | ) 39 | + const write = QIO.encaseP((fd: fs.FileHandle, buffer: Buffer) => 40 | + fd.write(buffer) 41 | + ) 42 | 43 | const managedFD = Managed.make(open('./abc.txt', 'w'), close) 44 | 45 | + managedFD.use(fd => write(fd, Buffer.from('DATA'))) 46 | ``` 47 | -------------------------------------------------------------------------------- /packages/website/pages/en/users.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | const React = require('react'); 9 | 10 | const CompLibrary = require('../../core/CompLibrary.js'); 11 | 12 | const Container = CompLibrary.Container; 13 | 14 | class Users extends React.Component { 15 | render() { 16 | const {config: siteConfig} = this.props; 17 | if ((siteConfig.users || []).length === 0) { 18 | return null; 19 | } 20 | 21 | const editUrl = `${siteConfig.repoUrl}/edit/master/website/siteConfig.js`; 22 | const showcase = siteConfig.users.map(user => ( 23 | 24 | {user.caption} 25 | 26 | )); 27 | 28 | return ( 29 |
30 | 31 |
32 |
33 |

Who is Using This?

34 |

This project is used by many folks

35 |
36 |
{showcase}
37 |

Are you using this project?

38 | 39 | Add your company 40 | 41 |
42 |
43 |
44 | ); 45 | } 46 | } 47 | 48 | module.exports = Users; 49 | -------------------------------------------------------------------------------- /packages/benchmarks/Stream/Stream.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tushar on 09/09/19 3 | */ 4 | 5 | import {QIO} from '@qio/core' 6 | import {Suite} from 'benchmark' 7 | 8 | import {PrintLn} from '../internals/PrintLn' 9 | import {qioRuntime} from '../internals/RunSuite' 10 | 11 | const suite = new Suite('QStream') 12 | 13 | const count = 1e6 14 | const arr = new Array() 15 | 16 | for (let i = 0; i < count; i++) { 17 | arr.push(i) 18 | } 19 | 20 | const qioIteration = QIO.encase((numbers: number[]) => { 21 | let sum = 0 22 | for (let i = 0; i < numbers.length; i++) { 23 | sum += numbers[i] 24 | } 25 | 26 | return sum 27 | }) 28 | const qioRecursion = (numbers: number[]) => { 29 | const itar = (i: number, sum: number): QIO => 30 | i === numbers.length 31 | ? QIO.resolve(sum) 32 | : QIO.call(itar, i + 1, sum + numbers[i]) 33 | 34 | return itar(0, 0) 35 | } 36 | 37 | suite 38 | 39 | .add( 40 | 'Recursion', 41 | (cb: IDefer) => 42 | qioRuntime.unsafeExecute(qioRecursion(arr), () => cb.resolve()), 43 | {defer: true} 44 | ) 45 | 46 | .add( 47 | 'Iterative', 48 | (cb: IDefer) => 49 | qioRuntime.unsafeExecute(qioIteration(arr), () => cb.resolve()), 50 | {defer: true} 51 | ) 52 | 53 | .on('cycle', (event: Event) => { 54 | PrintLn(String(event.target)) 55 | }) 56 | 57 | .on('complete', function (this: Suite): void { 58 | PrintLn( 59 | 'Fastest is ' + 60 | this.filter('fastest') 61 | .map((i: {name: string}) => i.name) 62 | .join('') 63 | ) 64 | }) 65 | .run() 66 | -------------------------------------------------------------------------------- /packages/scripts/lib/Symlink.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable: no-implicit-dependencies */ 2 | 3 | import {putStrLn, TTY} from '@qio/console' 4 | import {defaultRuntime, QIO} from '@qio/core' 5 | import {FS, FSEnv} from '@qio/fs' 6 | import * as p from 'path' 7 | 8 | interface IExitEnv { 9 | exit(code: number): void 10 | } 11 | interface ICwdEnc { 12 | cwd(): string 13 | } 14 | const PATH_PACKAGES = p.resolve(process.cwd(), 'packages') 15 | const PATH_NPM_IGNORE = '../../.npmignore' 16 | const qExit = (code: number) => QIO.access((_: IExitEnv) => _.exit(code)) 17 | const qSymLink = (path: string) => FS.symlink(PATH_NPM_IGNORE, path) 18 | const qSymLinkForced = (path: string) => FS.unlink(path).and(qSymLink(path)) 19 | const qCwd = QIO.access((_: ICwdEnc) => _.cwd()) 20 | 21 | const program = FS.readdir(PATH_PACKAGES).zipWithM(qCwd, (fileList, cwd) => 22 | QIO.par( 23 | fileList.map((F) => { 24 | const path = p.resolve(process.cwd(), 'packages', F, '.npmignore') 25 | 26 | return qSymLink(path) 27 | .and(putStrLn('OK', path)) 28 | .catch((err) => 29 | QIO.if( 30 | err.code === 'EEXIST', 31 | putStrLn('EXISTS', path) 32 | .and(qSymLinkForced(path)) 33 | .and(putStrLn('RETRY OK', path)), 34 | QIO.reject(err) 35 | ) 36 | ) 37 | }) 38 | ) 39 | ) 40 | 41 | defaultRuntime().unsafeExecute( 42 | program 43 | .catch((err) => putStrLn(err.message).and(qExit(1))) 44 | .provide({ 45 | cwd: () => process.cwd(), 46 | exit: (code) => process.exit(code), 47 | fs: FSEnv, 48 | tty: TTY, 49 | }) 50 | ) 51 | -------------------------------------------------------------------------------- /packages/docs/core/concurrency.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Concurrency 3 | --- 4 | 5 | QIO data structure provide various operators that can help manage concurrency. 6 | 7 | ## `.and()` 8 | 9 | Serial execution of effects can be performed through the `and` operator for eg: 10 | 11 | ```ts 12 | import {QIO} from '@qio/core' 13 | const putStrLn = QIO.encase(console.log) 14 | 15 | const foo = putStrLn('foo') 16 | const bar = putStrLn('bar') 17 | 18 | export const main = (): QIO => { 19 | return foo.and(bar) 20 | } 21 | ``` 22 | 23 | ## `.chain()` 24 | 25 | Sometimes its necessary to consume the response of the previous result to produce the effect. This can be done using the `chain` operator: 26 | 27 | ```ts 28 | export const main = (): QIO => { 29 | return getStrLn('Enter name:').chain(name => 30 | putStrLn('Welcome to the world of fp ' + name) 31 | ) 32 | } 33 | ``` 34 | 35 | ## `.par()` 36 | 37 | Similar to the `and` operator, the `par` operator helps run the two effects in parallel. For eg.: 38 | 39 | ```ts 40 | import {QIO} from '@qio/core' 41 | 42 | const foo = QIO.timeout('foo', 1000) 43 | const bar = QIO.timeout('bar', 1500) 44 | 45 | const fooBar = foo.par(bar) 46 | ``` 47 | 48 | The program `fooBar` completes in `1500`ms because both are executed in parallel. 49 | 50 | ## `.race()` 51 | 52 | Race allows two QIO instances to run in parallel and respond with the the one that completes/fails first. The one that is pending automatically gets cancelled. 53 | 54 | ```ts 55 | import {QIO} from '@qio/core' 56 | 57 | const foo = QIO.timeout('foo', 1000) 58 | const bar = QIO.timeout('bar', 1500) 59 | 60 | const fooBar = foo.race(bar) 61 | ``` 62 | -------------------------------------------------------------------------------- /packages/http/lib/Http.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable no-unbound-method */ 2 | 3 | import {QIO} from '@qio/core' 4 | import Axios, { 5 | AxiosBasicCredentials, 6 | AxiosError, 7 | AxiosProxyConfig, 8 | AxiosResponse, 9 | Method, 10 | ResponseType, 11 | } from 'axios' 12 | import * as http from 'http' 13 | import * as https from 'https' 14 | export * from 'axios' 15 | 16 | export interface IHttpRequestConfig { 17 | auth?: AxiosBasicCredentials 18 | data?: unknown 19 | headers?: { 20 | [k: string]: string 21 | } 22 | httpAgent?: http.Agent 23 | httpsAgent?: https.Agent 24 | maxContentLength?: number 25 | maxRedirects?: number 26 | method?: Method 27 | params?: { 28 | [k: string]: string | number 29 | } 30 | proxy?: AxiosProxyConfig | false 31 | responseType?: ResponseType 32 | url?: string 33 | withCredentials?: boolean 34 | xsrfCookieName?: string 35 | xsrfHeaderName?: string 36 | } 37 | 38 | export const request = (config: IHttpRequestConfig) => 39 | QIO.accessM((_: IHttpEnv) => _.http.request(config)) 40 | 41 | export interface IHttpEnv { 42 | http: { 43 | request(config: IHttpRequestConfig): QIO 44 | } 45 | } 46 | 47 | export const httpEnv = { 48 | request: (config: IHttpRequestConfig) => 49 | QIO.interruptible((res, rej) => { 50 | let cancelRequest = () => {} 51 | Axios.request({ 52 | ...config, 53 | cancelToken: new Axios.CancelToken( 54 | (cancel) => (cancelRequest = cancel) 55 | ), 56 | }).then(res, rej) 57 | 58 | return { 59 | cancel: () => { 60 | cancelRequest() 61 | }, 62 | } 63 | }), 64 | } 65 | -------------------------------------------------------------------------------- /packages/scripts/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. 5 | 6 | ## [31.1.1](https://github.com/tusharmath/qio/compare/v31.1.0...v31.1.1) (2020-10-19) 7 | 8 | **Note:** Version bump only for package @qio/scripts 9 | 10 | 11 | 12 | 13 | 14 | # [31.1.0](https://github.com/tusharmath/qio/compare/v31.0.1...v31.1.0) (2020-03-29) 15 | 16 | **Note:** Version bump only for package @qio/scripts 17 | 18 | 19 | 20 | 21 | 22 | # [31.0.0](https://github.com/tusharmath/qio/compare/v30.0.1...v31.0.0) (2020-01-28) 23 | 24 | **Note:** Version bump only for package @qio/scripts 25 | 26 | 27 | 28 | 29 | 30 | ## [30.0.1](https://github.com/tusharmath/qio/compare/v30.0.0...v30.0.1) (2019-12-31) 31 | 32 | **Note:** Version bump only for package @qio/scripts 33 | 34 | 35 | 36 | 37 | 38 | # [30.0.0](https://github.com/tusharmath/qio/compare/v29.2.1...v30.0.0) (2019-12-29) 39 | 40 | **Note:** Version bump only for package @qio/scripts 41 | 42 | 43 | 44 | 45 | 46 | ## [29.2.1](https://github.com/tusharmath/qio/compare/v29.2.0...v29.2.1) (2019-12-28) 47 | 48 | **Note:** Version bump only for package @qio/scripts 49 | 50 | 51 | 52 | 53 | 54 | # [29.1.0](https://github.com/tusharmath/qio/compare/v29.0.5...v29.1.0) (2019-12-27) 55 | 56 | **Note:** Version bump only for package @qio/scripts 57 | 58 | 59 | 60 | 61 | 62 | ## [29.0.2](https://github.com/tusharmath/qio/compare/v29.0.1...v29.0.2) (2019-12-24) 63 | 64 | 65 | ### Bug Fixes 66 | 67 | * **script:** change npmignore paths ([6acc109](https://github.com/tusharmath/qio/commit/6acc109cb3d36d719648b072ac586b8a5c89248d)) 68 | -------------------------------------------------------------------------------- /packages/website/pages/en/help.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | const React = require('react'); 9 | 10 | const CompLibrary = require('../../core/CompLibrary.js'); 11 | 12 | const Container = CompLibrary.Container; 13 | const GridBlock = CompLibrary.GridBlock; 14 | 15 | function Help(props) { 16 | const {config: siteConfig, language = ''} = props; 17 | const {baseUrl, docsUrl} = siteConfig; 18 | const docsPart = `${docsUrl ? `${docsUrl}/` : ''}`; 19 | const langPart = `${language ? `${language}/` : ''}`; 20 | const docUrl = doc => `${baseUrl}${docsPart}${langPart}${doc}`; 21 | 22 | const supportLinks = [ 23 | { 24 | content: `Learn more using the [documentation on this site.](${docUrl( 25 | 'doc1.html', 26 | )})`, 27 | title: 'Browse Docs', 28 | }, 29 | { 30 | content: 'Ask questions about the documentation and project', 31 | title: 'Join the community', 32 | }, 33 | { 34 | content: "Find out what's new with this project", 35 | title: 'Stay up to date', 36 | }, 37 | ]; 38 | 39 | return ( 40 |
41 | 42 |
43 |
44 |

Need help?

45 |
46 |

This project is maintained by a dedicated group of people.

47 | 48 |
49 |
50 |
51 | ); 52 | } 53 | 54 | module.exports = Help; 55 | -------------------------------------------------------------------------------- /packages/docs/comparison/benchmarks.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Benchmarks 3 | --- 4 | 5 | Comparison is done between [Fluture] and [Bluebird]. 6 | 7 | [fluture]: https://github.com/fluture-js/Fluture 8 | [bluebird]: https://github.com/petkaantonov/bluebird 9 | 10 | ## Constant 11 | 12 | ``` 13 | QIO x 2,940,723 ops/sec ±1.03% (79 runs sampled) 14 | Fluture x 162,035 ops/sec ±3.90% (44 runs sampled) 15 | bluebird x 6,126,678 ops/sec ±1.47% (76 runs sampled) 16 | Fastest is bluebird 17 | ``` 18 | 19 | ## CreateNestedMap 1000 20 | 21 | ``` 22 | QIO x 9,768 ops/sec ±1.75% (77 runs sampled) 23 | Fluture x 4,136 ops/sec ±1.41% (70 runs sampled) 24 | bluebird x 5,362 ops/sec ±1.17% (77 runs sampled) 25 | Fastest is QIO 26 | ``` 27 | 28 | ## Fibonacci: 20 29 | 30 | ``` 31 | Native x 624 ops/sec ±1.67% (90 runs sampled) 32 | QIO x 330 ops/sec ±2.29% (75 runs sampled) 33 | Fluture x 181 ops/sec ±1.66% (78 runs sampled) 34 | bluebird x 154 ops/sec ±1.98% (72 runs sampled) 35 | Fastest is Native 36 | ``` 37 | 38 | ## Map 39 | 40 | ``` 41 | QIO x 1,905,466 ops/sec ±1.66% (80 runs sampled) 42 | Fluture x 58,440 ops/sec ±42.61% (38 runs sampled) 43 | bluebird x 2,117,417 ops/sec ±1.07% (80 runs sampled) 44 | Fastest is bluebird 45 | ``` 46 | 47 | ## NestedChain 10000 48 | 49 | ``` 50 | QIO x 911 ops/sec ±1.52% (78 runs sampled) 51 | Fluture x 286 ops/sec ±1.29% (62 runs sampled) 52 | bluebird x 535 ops/sec ±1.23% (81 runs sampled) 53 | Fastest is QIO 54 | ``` 55 | 56 | ## NestedMap 1000 57 | 58 | ``` 59 | QIO x 9,766 ops/sec ±1.60% (77 runs sampled) 60 | Fluture x 4,230 ops/sec ±1.56% (75 runs sampled) 61 | bluebird x 5,367 ops/sec ±1.59% (76 runs sampled) 62 | Fastest is QIO 63 | ``` 64 | 65 | **Note:** These are micro benchmarks and don't represent a real world scenario. It's possible that in a practical scenario all the above libraries perform similarly. 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.com/tusharmath/qio.svg?branch=master)](https://travis-ci.com/tusharmath/qio) 2 | ![npm](https://img.shields.io/npm/v/@qio/core.svg) 3 | 4 | A type-safe, functional, performant, lawful, composable data structure that solves practical problems of effect-full code in node and browser. 5 | 6 | ## Usage 7 | 8 | ```ts 9 | import {QIO, defaultRuntime} from '@qio/core' 10 | 11 | const putStrLn = QIO.encase(console.log) 12 | 13 | const program = putStrLn('Hello World') 14 | 15 | defaultRuntime().unsafeExecute(program) 16 | ``` 17 | 18 | For more complex use cases checkout the [website](https://qio.netlify.com). 19 | 20 | ## Packages 21 | 22 | | Package | Description | Version | 23 | | ---------------- | ---------------------------------------------------------------------- | ----------------------------------------------------- | 24 | | **@qio/prelude** | The base library that's used internally by all the QIO based packages. | ![npm](https://img.shields.io/npm/v/@qio/prelude.svg) | 25 | | **@qio/core** | The core effect management library library. | ![npm](https://img.shields.io/npm/v/@qio/core.svg) | 26 | | **@qio/stream** | Purely functional streaming capabilities built on top of QIO. | ![npm](https://img.shields.io/npm/v/@qio/stream.svg) | 27 | | **@qio/console** | QIO based bindings to read and write to the terminal. | ![npm](https://img.shields.io/npm/v/@qio/console.svg) | 28 | | **@qio/http** | QIO based binding to manage HTTP request/responses safely. | ![npm](https://img.shields.io/npm/v/@qio/http.svg) | 29 | | **@qio/fs** | QIO based binding to manage HTTP request/responses safely. | ![npm](https://img.shields.io/npm/v/@qio/fs.svg) | 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "author": "Tushar Mathur ", 4 | "config": { 5 | "commitizen": { 6 | "path": "./node_modules/cz-lerna-changelog" 7 | } 8 | }, 9 | "description": "A type-safe functional module that solves practical IO problems for node and the browser.", 10 | "keywords": [ 11 | "io", 12 | "functional", 13 | "typescript", 14 | "pure", 15 | "promise" 16 | ], 17 | "license": "ISC", 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/tusharmath/qio.git" 21 | }, 22 | "scripts": { 23 | "lint": "tslint --project .", 24 | "postinstall": "yarn tsc -b && node packages/scripts/lib/Symlink.js", 25 | "prepublishOnly": "tsc -d", 26 | "prettier": "git ls-files | grep -E '.*\\.(ts)$' | xargs prettier --write --config=.prettierrc", 27 | "test": "mocha --config=packages/chai/.mocharc.yml ./packages/**/__tests__/*.test.js", 28 | "doc": "./scripts/docs.sh", 29 | "doc:typedoc": "yarn typedoc --plugin typedoc-plugin-markdown", 30 | "example": "node packages/example/guess-the-number/src/Run", 31 | "start": "yarn workspace @qio/website start", 32 | "commit:doc": "git add packages/docs && git commit -m 'docs(*): update docs \n\naffects: @qio/docs'", 33 | "commit:website": "git add packages/website && git commit -m 'website(*): update website \n\naffects: @qio/website'" 34 | }, 35 | "version": "0.0.0-development", 36 | "devDependencies": { 37 | "cz-lerna-changelog": "^2.0.2", 38 | "lerna": "^3.20.2", 39 | "mocha": "^8.2.0", 40 | "prettier": "^2.0.2", 41 | "semantic-release": "^17.0.4", 42 | "ts-codemod": "^4.0.4", 43 | "tslint": "^6.1.0", 44 | "tslint-config-prettier": "^1.18.0", 45 | "typedoc": "^0.19.2", 46 | "typedoc-plugin-markdown": "^3.0.10", 47 | "typescript": "^4.0.3", 48 | "typescript-tslint-plugin": "^0.5.5" 49 | }, 50 | "workspaces": [ 51 | "packages/*" 52 | ], 53 | "name": "qio" 54 | } 55 | -------------------------------------------------------------------------------- /packages/benchmarks/IO/Fibonacci.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable: promise-function-async strict-comparisons */ 2 | 3 | import {effect as T} from '@matechs/effect' 4 | import {QIO} from '@qio/core' 5 | import {Promise} from 'bluebird' 6 | import * as Fluture from 'fluture' 7 | 8 | import {RunSuite} from '../internals/RunSuite' 9 | 10 | /** 11 | * Normal Fibonacci Implementation 12 | */ 13 | export const fib = (n: bigint): bigint => { 14 | if (n < 2n) { 15 | return 1n 16 | } 17 | 18 | return fib(n - 1n) + fib(n - 2n) 19 | } 20 | 21 | /** 22 | * Fluture based implementation 23 | */ 24 | export const fibFluture = ( 25 | n: bigint 26 | ): Fluture.FutureInstance => { 27 | if (n < 2n) { 28 | return Fluture.resolve(1n) 29 | } 30 | 31 | return Fluture.chain((a: bigint) => 32 | Fluture.map((b: bigint) => a + b)(fibFluture(n - 2n)) 33 | )(fibFluture(n - 1n)) 34 | } 35 | 36 | /** 37 | * QIO based implementation 38 | */ 39 | export const fibQIO = (n: bigint): QIO => { 40 | if (n < 2n) { 41 | return QIO.resolve(1n) 42 | } 43 | 44 | return fibQIO(n - 1n).chain((a) => fibQIO(n - 2n).map((b) => a + b)) 45 | } 46 | 47 | /** 48 | * Bluebird based implementation 49 | */ 50 | export const fibBird = (n: bigint): Promise => { 51 | if (n < 2n) { 52 | return Promise.resolve(1n) 53 | } 54 | 55 | return fibBird(n - 1n).then((a) => fibBird(n - 2n).then((b) => a + b)) 56 | } 57 | 58 | /** 59 | * Matechs based implementation 60 | */ 61 | export const fibMatechs = (n: bigint): T.Effect => { 62 | if (n < BigInt(2)) { 63 | return T.pure(BigInt(1)) 64 | } 65 | 66 | return T.effect.chain(fibMatechs(n - BigInt(1)), (a) => 67 | T.effect.map(fibMatechs(n - BigInt(2)), (b) => a + b) 68 | ) 69 | } 70 | 71 | const count = 20n 72 | RunSuite(`Fibonacci: ${String(count)}`, { 73 | bluebird: () => fibBird(count), 74 | fluture: () => fibFluture(count), 75 | matechs: () => fibMatechs(count), 76 | native: () => fib(count), 77 | qio: () => fibQIO(count), 78 | }) 79 | -------------------------------------------------------------------------------- /packages/benchmarks/internals/RunSuite.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable: no-unbound-method */ 2 | import * as T from '@matechs/effect' 3 | import {defaultRuntime, QIO} from '@qio/core' 4 | import {noop} from '@qio/prelude' 5 | import {Suite} from 'benchmark' 6 | import {fork, FutureInstance} from 'fluture' 7 | 8 | import {PrintLn} from './PrintLn' 9 | 10 | export const qioRuntime = defaultRuntime() 11 | 12 | /** 13 | * Native runs native benchmarks. 14 | */ 15 | export const RUN_NATIVE = false 16 | 17 | export const RunSuite = ( 18 | name: string, 19 | test: { 20 | bluebird(): PromiseLike 21 | fluture(): FutureInstance 22 | matechs?(): T.effect.Effect 23 | native?(): void 24 | qio(): QIO 25 | } 26 | ) => { 27 | PrintLn('##', name) 28 | PrintLn('```') 29 | const suite = new Suite(name) 30 | 31 | if (typeof test.native === 'function' && RUN_NATIVE) { 32 | suite.add('Native', () => { 33 | ;(test as {native(): void}).native() 34 | }) 35 | } 36 | 37 | if (typeof test.matechs === 'function') { 38 | const F = test.matechs 39 | suite.add( 40 | 'Matechs', 41 | (cb: IDefer) => T.effect.run(F(), () => cb.resolve()), 42 | {defer: true} 43 | ) 44 | } 45 | 46 | suite 47 | .add( 48 | 'QIO', 49 | (cb: IDefer) => qioRuntime.unsafeExecute(test.qio(), () => cb.resolve()), 50 | {defer: true} 51 | ) 52 | .add( 53 | 'Fluture', 54 | (cb: IDefer) => fork(noop)(() => cb.resolve())(test.fluture()), 55 | {defer: true} 56 | ) 57 | .add('bluebird', (cb: IDefer) => test.bluebird().then(() => cb.resolve()), { 58 | defer: true, 59 | }) 60 | 61 | .on('cycle', (event: Event) => { 62 | PrintLn(String(event.target)) 63 | }) 64 | 65 | .on('complete', function (this: Suite): void { 66 | PrintLn( 67 | 'Fastest is ' + 68 | this.filter('fastest') 69 | .map((i: {name: string}) => i.name) 70 | .join('') + 71 | '\n```' 72 | ) 73 | }) 74 | .run() 75 | } 76 | -------------------------------------------------------------------------------- /packages/prototype/FServer.ts: -------------------------------------------------------------------------------- 1 | import {defaultRuntime, IRuntime, Managed, QIO} from '@qio/core' 2 | import * as http from 'http' 3 | 4 | const Exit = QIO.encase((message: Error) => { 5 | process.exit(1) 6 | }) 7 | interface IQIOServerOptions { 8 | mounted: { 9 | [k: string]: (req: http.IncomingMessage) => QIO 10 | } 11 | port: number 12 | } 13 | class QIOServerBuilder { 14 | public static of(): QIOServerBuilder { 15 | return new QIOServerBuilder({port: 0, mounted: {}}) 16 | } 17 | private constructor(private readonly options: IQIOServerOptions) {} 18 | public get serve(): QIO { 19 | return Managed.make( 20 | QIO.runtime().encase((RTM) => new QIOServer(RTM, this.options)), 21 | (server) => server.close 22 | ).use(QIO.never) 23 | } 24 | 25 | public bind(port: number): QIOServerBuilder { 26 | return new QIOServerBuilder({...this.options, port}) 27 | } 28 | public mount( 29 | path: string, 30 | fn: (req: http.IncomingMessage) => QIO 31 | ): QIOServerBuilder { 32 | return new QIOServerBuilder({ 33 | ...this.options, 34 | mounted: {...this.options.mounted, [path]: fn}, 35 | }) 36 | } 37 | } 38 | 39 | class QIOServer { 40 | private readonly server: http.Server 41 | public constructor( 42 | private readonly RTM: IRuntime, 43 | private readonly options: IQIOServerOptions 44 | ) { 45 | this.server = http.createServer(this.onRequest).listen(this.options.port) 46 | } 47 | public get close(): QIO { 48 | return QIO.uninterruptible((res, rej) => () => 49 | this.server.close((E) => (E !== undefined ? rej(E) : res())) 50 | ).catch(Exit) 51 | } 52 | 53 | private readonly onRequest = ( 54 | req: http.IncomingMessage, 55 | res: http.ServerResponse 56 | ) => { 57 | if (req.url !== undefined && this.options.mounted.hasOwnProperty(req.url)) { 58 | this.RTM.unsafeExecute( 59 | this.options.mounted[req.url](req).encase((chunk) => res.end(chunk)) 60 | ) 61 | } 62 | } 63 | } 64 | 65 | const runtime = defaultRuntime() 66 | runtime.unsafeExecute( 67 | QIOServerBuilder.of().mount('/greet', () => QIO.resolve('Hello World!')).serve 68 | ) 69 | -------------------------------------------------------------------------------- /packages/example/guess-the-number/src/Program.ts: -------------------------------------------------------------------------------- 1 | import {getStrLn, putStrLn} from '@qio/console' 2 | import {QIO} from '@qio/core' 3 | import {QStream} from '@qio/stream' 4 | 5 | const MAX_NUMBER = 6 6 | const MIN_NUMBER = 1 7 | 8 | /** 9 | * Provides access to the Math env 10 | */ 11 | export interface IMath { 12 | math: { 13 | random(): number 14 | } 15 | } 16 | 17 | /** 18 | * Generates a random number. 19 | */ 20 | const randomNumber = QIO.access((env: IMath) => env.math.random()) 21 | 22 | /** 23 | * Returns a random number within the provide range. 24 | */ 25 | const randomInt = (min: number, max: number) => 26 | randomNumber.map((_) => Math.round(_ * (max - min) + min)) 27 | 28 | /** 29 | * Keeps taking a numeric input from the player. 30 | */ 31 | const inputNumber = QStream.produce( 32 | getStrLn(`Enter a number between ${MIN_NUMBER} & ${MAX_NUMBER}: `) 33 | ) 34 | .map((_) => parseInt(_, 10)) 35 | .filter( 36 | (input) => 37 | Number.isFinite(input) && input >= MIN_NUMBER && input <= MAX_NUMBER 38 | ) 39 | 40 | /** 41 | * Takes the player's name. 42 | */ 43 | const inputName = getStrLn('Enter your name: ').chain((name) => 44 | putStrLn(`Welcome to the world of functional programming, ${name}!`) 45 | ) 46 | 47 | /** 48 | * Checks if the use wants to continue with the game. 49 | */ 50 | export const canContinue = getStrLn( 51 | 'Press ⏎ to continue (or will exit in 3sec): ' 52 | ) 53 | .const(true) 54 | .race(putStrLn('\nGood bye!').delay(3000).const(false)) 55 | 56 | /** 57 | * Takes an input integer and checks if it matches with a random number. 58 | */ 59 | const checkWithRandom = (guess: number) => 60 | randomInt(MIN_NUMBER, MAX_NUMBER).chain((random) => 61 | guess === random 62 | ? putStrLn(`You guessed it right!`) 63 | : putStrLn(`Sorry, the correct answer is ${random}`) 64 | ) 65 | 66 | /** 67 | * Takes in the the player's name and greets them with a message. 68 | */ 69 | const greet = putStrLn('Greetings!').and(inputName) 70 | 71 | /** 72 | * Starts of the game in a loop until the user decides to exit. 73 | */ 74 | const gameLoop = inputNumber 75 | .mapM(checkWithRandom) 76 | .forEachWhile(() => canContinue) 77 | 78 | export const program = greet.and(gameLoop) 79 | -------------------------------------------------------------------------------- /packages/core/lib/main/Instructions.ts: -------------------------------------------------------------------------------- 1 | import {ICancellable} from 'ts-scheduler' 2 | 3 | import {CB} from '../internals/CB' 4 | import {FiberRuntime} from '../runtimes/FiberRuntime' 5 | 6 | /** 7 | * @ignore 8 | */ 9 | export enum Tag { 10 | Access, 11 | Async, 12 | Call, 13 | Capture, 14 | Catch, 15 | Chain, 16 | Constant, 17 | Fork, 18 | Map, 19 | Never, 20 | Provide, 21 | Reject, 22 | Runtime, 23 | Try, 24 | TryM, 25 | } 26 | 27 | export interface ICall { 28 | i1: T 29 | tag: Tag.Call 30 | i0(...t: T): Instruction 31 | } 32 | 33 | export interface IConstant { 34 | i0: A 35 | tag: Tag.Constant 36 | } 37 | export interface IReject { 38 | i0: A 39 | tag: Tag.Reject 40 | } 41 | export interface ITry { 42 | tag: Tag.Try 43 | i0(a: A): B 44 | } 45 | export interface ITryM { 46 | tag: Tag.TryM 47 | i0(A: A): Instruction 48 | } 49 | export interface IMap { 50 | i0: Instruction 51 | tag: Tag.Map 52 | i1(a: A): B 53 | } 54 | export interface IChain { 55 | i0: Instruction 56 | tag: Tag.Chain 57 | i1(a: A): Instruction 58 | } 59 | export interface ICatch { 60 | i0: Instruction 61 | tag: Tag.Catch 62 | i1(E: E): Instruction 63 | } 64 | export interface IAsync { 65 | tag: Tag.Async 66 | 67 | i0(res: CB, rej: CB): ICancellable 68 | } 69 | export interface INever { 70 | tag: Tag.Never 71 | } 72 | export interface IFork { 73 | i0: Instruction 74 | i1: FiberRuntime 75 | tag: Tag.Fork 76 | } 77 | export interface IAccess { 78 | tag: Tag.Access 79 | i0(X: X): Y 80 | } 81 | export interface IProvide { 82 | i0: Instruction 83 | i1: R 84 | tag: Tag.Provide 85 | } 86 | 87 | export interface ICapture { 88 | tag: Tag.Capture 89 | i0(i: A): Instruction 90 | } 91 | 92 | export interface IRTime { 93 | tag: Tag.Runtime 94 | } 95 | 96 | /** 97 | * @ignore 98 | */ 99 | export type Instruction = 100 | | IAccess 101 | | IAsync 102 | | ICall 103 | | ICapture 104 | | ICatch 105 | | IChain 106 | | IConstant 107 | | IFork 108 | | IMap 109 | | INever 110 | | IProvide 111 | | IReject 112 | | IRTime 113 | | ITry 114 | | ITryM 115 | -------------------------------------------------------------------------------- /packages/core/lib/internals/YieldStrategy.ts: -------------------------------------------------------------------------------- 1 | import {ICancellable, IScheduler} from 'ts-scheduler' 2 | 3 | import {FiberConfig, YieldStrategyTag} from './FiberConfig' 4 | 5 | /** 6 | * Base type Fiber Yield Strategy 7 | * Fiber can use various mechanisms to decide when to yield. 8 | */ 9 | export abstract class YieldStrategy { 10 | public static create( 11 | scheduler: IScheduler, 12 | config: FiberConfig 13 | ): YieldStrategy { 14 | switch (config.tag) { 15 | case YieldStrategyTag.INS_COUNT: 16 | return new YieldCount(scheduler, config.maxInstructionCount) 17 | case YieldStrategyTag.DURATION: 18 | return new YieldDuration(scheduler, config.maxDuration) 19 | default: 20 | return new YieldCount(scheduler) 21 | } 22 | } 23 | public abstract canYield(): boolean 24 | public abstract init(): void 25 | public insert( 26 | cb: (...T: T) => unknown, 27 | ...T: T 28 | ): ICancellable { 29 | this.init() 30 | 31 | return this.defer(cb, ...T) 32 | } 33 | protected abstract defer( 34 | cb: (...t: T) => unknown, 35 | ...t: T 36 | ): ICancellable 37 | } 38 | /** 39 | * The strategy signals yielding after a certain number of checks. 40 | */ 41 | export class YieldCount extends YieldStrategy { 42 | public readonly maxCount: number 43 | private count = 0 44 | public constructor( 45 | private readonly scheduler: IScheduler, 46 | maxCount: number = Number.MAX_SAFE_INTEGER 47 | ) { 48 | super() 49 | this.maxCount = Math.min(Math.max(1, maxCount), Number.MAX_SAFE_INTEGER) 50 | } 51 | public canYield(): boolean { 52 | const R = this.count > this.maxCount 53 | this.count++ 54 | 55 | return R 56 | } 57 | public defer( 58 | cb: (...T: T) => unknown, 59 | ...T: T 60 | ): ICancellable { 61 | return this.scheduler.asap(cb, ...T) 62 | } 63 | public init(): void { 64 | this.count = 0 65 | } 66 | } 67 | /** 68 | * Yields after a quantified amount of time has passed. 69 | */ 70 | export class YieldDuration extends YieldStrategy { 71 | private start = this.scheduler.now() 72 | public constructor( 73 | private readonly scheduler: IScheduler, 74 | public readonly maxDuration: number 75 | ) { 76 | super() 77 | } 78 | public canYield(): boolean { 79 | return this.scheduler.now() - this.start >= this.maxDuration 80 | } 81 | public defer( 82 | cb: (...T: T) => unknown, 83 | ...T: T 84 | ): ICancellable { 85 | return this.scheduler.asap(cb, ...T) 86 | } 87 | public init(): void { 88 | this.start = this.scheduler.now() 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /packages/docs/core/usage.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Usage 3 | sidebar_label: Usage 4 | --- 5 | 6 | [concepts]: ../concepts/introduction 7 | 8 | QIO is merely a data structure that represents a side-effect. It doesn't actually perform the side effect itself. The effect is performed by the QIO Runtime lazily. It helps convert **impure functions** to **pure functions**. 9 | 10 | `QIO` uses three type params to represent an effect safely: 11 | 12 | ```ts 13 | interface QIO { 14 | // ... 15 | } 16 | ``` 17 | 18 | | Parameter | | 19 | | :-------: | --------------------------------------------------------------------------- | 20 | | `A` | The type of the success value that will be emitted by the IO on completion. | 21 | | `E` | The error types that can be emitted while this IO is executing. | 22 | | `R` | Represents the type of environment needed to execute this IO. | 23 | 24 | Using these three type params you can fairly represent any side-effect. For eg. consider `console.log` 25 | 26 | ```ts 27 | const greet = () => console.log('Hello World!') // void 28 | ``` 29 | 30 | `greet` can be represented using QIO as: 31 | 32 | ```ts 33 | import {QIO} from '@qio/core' 34 | 35 | const greetIO = QIO.lift(greet) // QIO 36 | ``` 37 | 38 | | Parameter | Value | | 39 | | --------- | --------- | --------------------------------------------- | 40 | | `A` | `void` | The output of running the program is nothing. | 41 | | `E` | `never` | Printing anything on console never fails. | 42 | | `R` | `unknown` | since `console.log` works everywhere. | 43 | 44 | ## Creating a QIO 45 | 46 | There are multiple ways through which you can create an instance of QIO. 47 | 48 | [api documentation]: api/classes/qio.md 49 | 50 | One of the easiest ways to create a QIO is through [QIO.encase]. 51 | 52 | [qio.encase]: api/classes/qio.md#encase 53 | 54 | ```ts 55 | + import {QIO} from '@qio/core' 56 | + 57 | + const putStrLn = QIO.encase(console.log) 58 | + const greet = putStrLn('Hello World') 59 | ``` 60 | 61 | `greet` returns a pure data structure of the type `QIO` which represents a side-effect, that: 62 | 63 | 1. Resolves with a value of type _void_. 64 | 2. It _never_ fails. 65 | 3. Can execute in any _unknown_ environment. 66 | 67 | ## Executing QIO 68 | 69 | Execution of QIO happens through a [Runtime]. 70 | 71 | [runtime]: ../api/globals#const-defaultruntime 72 | 73 | ```ts 74 | - import {QIO} from '@qio/core' 75 | + import {QIO, defaultRuntime} from '@qio/core' 76 | 77 | const putStrLn = QIO.encase(console.log) 78 | const greet = putStrLn('Hello World') 79 | + defaultRuntime().unsafeExecute(greet) 80 | ``` 81 | -------------------------------------------------------------------------------- /packages/console/lib/Console.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tushar on 07/11/19 3 | */ 4 | import {Managed, QIO} from '@qio/core' 5 | import * as R from 'readline' 6 | 7 | // Data Types 8 | interface ITextTerminal { 9 | getStrLn(question?: string): QIO 10 | putStrLn(...t: unknown[]): QIO 11 | } 12 | interface ITextTerminalTest extends ITextTerminal { 13 | stdout: string[] 14 | } 15 | 16 | // Environments 17 | interface IReadLineEnv { 18 | readLine: { 19 | createInterface(_: { 20 | input: NodeJS.ReadStream 21 | output: NodeJS.WriteStream 22 | }): R.Interface 23 | } 24 | } 25 | 26 | interface IProcessEnv { 27 | process: NodeJS.Process 28 | } 29 | 30 | interface IConsoleEnv { 31 | console: { 32 | log(...t: unknown[]): void 33 | } 34 | } 35 | 36 | export interface ITextTerminalEnv { 37 | tty: ITextTerminal 38 | } 39 | 40 | // ReadLine Utilities 41 | const openRL = QIO.env().zipWithM( 42 | QIO.env(), 43 | QIO.encase((R1, R2) => 44 | R1.readLine.createInterface({ 45 | input: R2.process.stdin, 46 | output: R2.process.stdout, 47 | }) 48 | ) 49 | ) 50 | const closeRL = QIO.encase((RL: R.Interface) => RL.close()) 51 | const managedRL = Managed.make(openRL, closeRL) 52 | 53 | const putStrLn0 = (...t: unknown[]) => 54 | QIO.access((_: IConsoleEnv) => _.console.log(...t)) 55 | 56 | const getStrLn0 = (question: string = '') => 57 | managedRL.use((RL) => 58 | QIO.uninterruptible((cb) => RL.question(question, cb)) 59 | ) 60 | 61 | export const TTY: ITextTerminal = { 62 | getStrLn: QIO.pipeEnv(getStrLn0, {process, readLine: R}), 63 | putStrLn: QIO.pipeEnv(putStrLn0, {console}), 64 | } 65 | 66 | /** 67 | * Mock implementation of ITextTerminal 68 | */ 69 | export const testTTY = ( 70 | input: {[k: string]: QIO} = {} 71 | ): ITextTerminalTest => { 72 | const stdout = new Array() 73 | const log = QIO.encase((str: string) => void stdout.push(str)) 74 | const append = QIO.encase((str: string) => { 75 | const elm = stdout.pop() 76 | stdout.push(elm === undefined ? str : elm + str) 77 | }) 78 | 79 | return { 80 | getStrLn: (question: string) => 81 | input.hasOwnProperty(question) 82 | ? log(question).and(input[question].tapM(append)) 83 | : log(question).and(QIO.never()), 84 | putStrLn: (...t: unknown[]) => 85 | QIO.lift(() => void stdout.push(t.join(' '))), 86 | stdout, 87 | } 88 | } 89 | 90 | /** 91 | * Takes input from the player through the stdin stream. 92 | */ 93 | export const getStrLn = (question: string) => 94 | QIO.accessM((_: ITextTerminalEnv) => _.tty.getStrLn(question)) 95 | 96 | /** 97 | * Outputs anything passed as arguments to the stdout stream 98 | */ 99 | export const putStrLn = (...t: unknown[]) => 100 | QIO.accessM((_: ITextTerminalEnv) => _.tty.putStrLn(...t)) 101 | -------------------------------------------------------------------------------- /packages/core/lib/main/Await.ts: -------------------------------------------------------------------------------- 1 | import {debug} from 'debug' 2 | import {DoublyLinkedList, Either, Option} from 'standard-data-structures' 3 | 4 | import {CB} from '../internals/CB' 5 | import {IDGenerator} from '../internals/IDGenerator' 6 | 7 | import {QIO} from './QIO' 8 | 9 | enum AwaitStatus { 10 | PENDING, 11 | STARTED, 12 | COMPLETED, 13 | } 14 | const AWAIT_ID = new IDGenerator() 15 | const D = (id: number, scope: string, ...t: unknown[]) => 16 | debug('qio:await')(id, scope, ...t) 17 | 18 | /** 19 | * A special data structure that can be set only once. 20 | * Any get operation on Await will "wait" for a set to happen first. 21 | * Its kind of like a Promise, because it can be set only once. 22 | * @typeparam E Errors thrown 23 | * @typeparam A Success value 24 | */ 25 | export class Await { 26 | public static of(): QIO> { 27 | return QIO.lift(() => { 28 | const awt = new Await() 29 | D(awt.id, 'this.constructor()') 30 | 31 | return awt 32 | }) 33 | } 34 | public readonly id = AWAIT_ID.create() 35 | private flag = AwaitStatus.PENDING 36 | private readonly Q = DoublyLinkedList.of<[CB, CB]>() 37 | private result: Option> = Option.none() 38 | public get get(): QIO { 39 | return QIO.tryM(() => 40 | this.result 41 | .map((S) => S.reduce>(QIO.reject, (XX) => QIO.resolve(XX))) 42 | .getOrElse(this.wait) 43 | ) 44 | } 45 | public get isSet(): QIO { 46 | return QIO.lift(() => this.flag === AwaitStatus.COMPLETED) 47 | } 48 | private get wait(): QIO { 49 | return QIO.interruptible((res, rej) => { 50 | const id = this.Q.add([res, rej]) 51 | D(this.id, 'add wait') 52 | D(this.id, 'this.Q.length', this.Q.length) 53 | 54 | return { 55 | cancel: () => { 56 | this.Q.remove(id) 57 | D(this.id, 'remove wait') 58 | D(this.id, 'this.Q.length', this.Q.length) 59 | }, 60 | } 61 | }) 62 | } 63 | public set(io: QIO): QIO { 64 | return QIO.tryM(() => { 65 | D(this.id, 'set', 'status', AwaitStatus[this.flag]) 66 | if (this.flag > AwaitStatus.PENDING) { 67 | return QIO.resolve(false) 68 | } 69 | 70 | this.flag = AwaitStatus.STARTED 71 | D(this.id, 'set', 'status', AwaitStatus[this.flag]) 72 | 73 | return io.asEither.encase((either) => { 74 | this.flag = AwaitStatus.COMPLETED 75 | D(this.id, 'set', 'status', AwaitStatus[this.flag]) 76 | this.result = Option.some(either) 77 | while (this.Q.length > 0) { 78 | const node = this.Q.shift() 79 | 80 | if (Option.isSome(node)) { 81 | either.reduce(node.value[1], node.value[0]) 82 | } 83 | } 84 | 85 | return true 86 | }) 87 | }) 88 | } 89 | public setTo(a: A): QIO { 90 | return this.set(QIO.resolve(a)) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /packages/fs/lib/FS.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable strict-boolean-expressions no-unbound-method no-for-in */ 2 | 3 | import {QIO} from '@qio/core' 4 | import * as fs from 'fs' 5 | 6 | type QIOErrno = QIO 7 | 8 | /** 9 | * Creates an uninterruptible effect from an async function 10 | */ 11 | const U = ( 12 | fn: (CB: (err: NodeJS.ErrnoException | null, data: A) => void) => void 13 | ): QIOErrno => 14 | QIO.uninterruptible((res, rej) => 15 | fn((err, data) => (err ? rej(err) : res(data))) 16 | ) 17 | 18 | /** 19 | * Creates an uninterruptible effect from an async function that returns `void` 20 | */ 21 | const V = ( 22 | fn: (CB: (err: NodeJS.ErrnoException | null) => void) => void 23 | ): QIOErrno => 24 | QIO.uninterruptible((res, rej) => fn((err) => (err ? rej(err) : res()))) 25 | 26 | /** 27 | * An environment for FS 28 | */ 29 | interface IFSEnv { 30 | fs: { 31 | [k in K]: (...T: T) => QIO 32 | } 33 | } 34 | 35 | /** 36 | * TODO: Keep adding all the node fs methods on by one. 37 | */ 38 | export const FSEnv = { 39 | open: (path: fs.PathLike, mode: string, flag?: number) => 40 | U((CB) => fs.open(path, mode, flag, CB)), 41 | 42 | close: (fd: number): QIOErrno => V((CB) => fs.close(fd, CB)), 43 | 44 | readFile: ( 45 | path: fs.PathLike | number, 46 | options?: {encoding?: null; flag?: string} 47 | ) => U((CB) => fs.readFile(path, options, CB)), 48 | 49 | writeFile: ( 50 | path: fs.PathLike | number, 51 | data: string | NodeJS.ArrayBufferView, 52 | options: fs.WriteFileOptions = {} 53 | ) => V((CB) => fs.writeFile(path, data, options, CB)), 54 | 55 | readdir: ( 56 | path: string, 57 | options?: 58 | | {encoding: BufferEncoding | null; withFileTypes?: false} 59 | | BufferEncoding 60 | ) => U((CB) => fs.readdir(path, options, CB)), 61 | 62 | unlink: (path: fs.PathLike): QIOErrno => V((CB) => fs.unlink(path, CB)), 63 | 64 | symlink: (target: fs.PathLike, path: fs.PathLike) => 65 | V((cb) => fs.symlink(target, path, cb)), 66 | 67 | copyFile: (src: fs.PathLike, dest: fs.PathLike) => 68 | V((cb) => fs.copyFile(src, dest, cb)), 69 | } 70 | 71 | type FSWithEnv = { 72 | [k in keyof O]: O[k] extends (...t: infer T) => QIO 73 | ? (...t: T) => QIO QIO}}> 74 | : never 75 | } 76 | 77 | interface ISpec { 78 | [k: string]: (...t: never[]) => unknown 79 | } 80 | 81 | const createFSWithEnv = (S: S): FSWithEnv => { 82 | const out: {[k in keyof S]?: (...t: unknown[]) => unknown} = {} 83 | 84 | for (const key in S) { 85 | if (S.hasOwnProperty(key)) { 86 | out[key] = (...t: unknown[]) => 87 | QIO.accessM((_: IFSEnv) => 88 | _.fs[key](...t) 89 | ) 90 | } 91 | } 92 | 93 | return out as FSWithEnv 94 | } 95 | 96 | export const FS = createFSWithEnv(FSEnv) 97 | -------------------------------------------------------------------------------- /packages/console/__tests__/Console.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by tushar on 07/11/19 3 | */ 4 | 5 | import {QIO, testRuntime} from '@qio/core' 6 | import {assert} from 'chai' 7 | 8 | import {getStrLn, putStrLn, testTTY} from '../lib/Console' 9 | 10 | describe('Console', () => { 11 | describe('putStrLn()', () => { 12 | it('should log content on the screen', () => { 13 | const tty = testTTY() 14 | testRuntime().unsafeExecuteSync(putStrLn('HELLO WORLD').provide({tty})) 15 | const actual = tty.stdout 16 | const expected = ['HELLO WORLD'] 17 | assert.deepStrictEqual(actual, expected) 18 | }) 19 | 20 | it('should join multiple args', () => { 21 | const tty = testTTY() 22 | testRuntime().unsafeExecuteSync(putStrLn('HELLO', 'WORLD').provide({tty})) 23 | const actual = tty.stdout 24 | const expected = ['HELLO WORLD'] 25 | assert.deepStrictEqual(actual, expected) 26 | }) 27 | 28 | it('should convert data to string', () => { 29 | const tty = testTTY() 30 | testRuntime().unsafeExecuteSync(putStrLn({}).provide({tty})) 31 | const actual = tty.stdout 32 | const expected = ['[object Object]'] 33 | assert.deepStrictEqual(actual, expected) 34 | }) 35 | }) 36 | 37 | describe('getStrLn()', () => { 38 | it('should read from the provided questions', () => { 39 | const tty = testTTY({'name ?': QIO.resolve('QIO')}) 40 | 41 | const actual = testRuntime().unsafeExecuteSync( 42 | getStrLn('name ?').provide({tty}) 43 | ) 44 | const expected = 'QIO' 45 | 46 | assert.strictEqual(actual, expected) 47 | }) 48 | 49 | it('should print on stdout', () => { 50 | const tty = testTTY({'name: ': QIO.resolve('QIO')}) 51 | 52 | testRuntime().unsafeExecuteSync(getStrLn('name: ').provide({tty})) 53 | 54 | const actual = tty.stdout 55 | const expected = ['name: QIO'] 56 | 57 | assert.deepStrictEqual(actual, expected) 58 | }) 59 | 60 | context('no answer provided', () => { 61 | it('should keep waiting', () => { 62 | const input = ['P', 'Q'] 63 | const tty = testTTY({A: QIO.lift(() => input.pop() as string)}) 64 | 65 | const actual = testRuntime().unsafeExecuteSync( 66 | getStrLn('B').provide({tty}) 67 | ) 68 | 69 | assert.isUndefined(actual) 70 | }) 71 | 72 | it('should print on stdout', () => { 73 | const tty = testTTY({'name: ': QIO.never()}) 74 | 75 | testRuntime().unsafeExecuteSync(getStrLn('name: ').provide({tty})) 76 | 77 | const actual = tty.stdout 78 | const expected = ['name: '] 79 | 80 | assert.deepStrictEqual(actual, expected) 81 | }) 82 | 83 | it('should print question', () => { 84 | const tty = testTTY({}) 85 | 86 | testRuntime().unsafeExecuteSync(getStrLn('name: ').provide({tty})) 87 | 88 | const actual = tty.stdout 89 | const expected = ['name: '] 90 | 91 | assert.deepStrictEqual(actual, expected) 92 | }) 93 | }) 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /packages/docs/core/stack-safety.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Stack Safety 3 | --- 4 | 5 | Consider the following recursive **fibonacci** implementation in typescript: 6 | 7 | ```ts 8 | const fib = (a: bigint, l: bigint = 0n, r: bigint = 1n): bigint => { 9 | if (a <= 2n) { 10 | return l + r 11 | } 12 | 13 | return fib(a - 1n, r, l + r) 14 | } 15 | 16 | fib(10n) // 55n 17 | ``` 18 | 19 | This recursion eventually causes a **stack overflow** exception at around `fib(6982n)`. 20 | 21 | ```error 22 | /fib.js:2 23 | const fib = (a, l = 0n, r = 1n) => { 24 | ^ 25 | 26 | RangeError: Maximum call stack size exceeded 27 | at fib (/fib.js:2:13) 28 | at fib (/fib.js:6:12) 29 | at fib (/fib.js:6:12) 30 | at fib (/fib.js:6:12) 31 | at fib (/fib.js:6:12) 32 | at fib (/fib.js:6:12) 33 | at fib (/fib.js:6:12) 34 | at fib (/fib.js:6:12) 35 | at fib (/fib.js:6:12) 36 | at fib (/fib.js:6:12) 37 | ``` 38 | 39 | ## Lazy Evaluation 40 | 41 | Using `QIO.lazy` we can convert the recursive calls, into lazy ones. That way we make sure the function isn't immediately invoked thus guaranteeing stack safety. 42 | 43 | ```diff 44 | + import {QIO} from '@qio/core' 45 | + 46 | - const fib = (a: bigint, l: bigint = 0n, r: bigint = 1n): bigint => { 47 | + const fib = QIO.lazy((a: bigint, l: bigint = 0n, r: bigint = 1n): QIO => { 48 | if (a <= 2n) { 49 | - return l + r 50 | + return QIO.resolve(l + r) 51 | } 52 | 53 | return fib(a - 1n, r, l + r) 54 | - } 55 | + }) 56 | 57 | - fib(10n) // 55n 58 | + fib(10n) // QIO 59 | ``` 60 | 61 | Calling `fib(10n)` doesn't actually do anything, it just creates a `QIO`. This `QIO` when evaluated using the `runtime` internally keeps calling the function passed to `QIO.lazy` iteratively. 62 | 63 | ## Actual Execution 64 | 65 | ```diff 66 | - import {QIO} from '@qio/core' 67 | + import {QIO, defaultRuntime} from '@qio/core' 68 | 69 | const fib = QIO.lazy((a: bigint, l: bigint = 0n, r: bigint = 1n): QIO => { 70 | if (a <= 2n) { 71 | return QIO.resolve(l + r) 72 | } 73 | 74 | return fib(a - 1n, r, l + r) 75 | }) 76 | 77 | - fib(1_000_000n) // QIO 78 | + const program = fib(1_000_000n) // QIO 79 | + defaultRuntime().unsafeExecute(program) // A 208,988 digit long number 80 | ``` 81 | 82 | Now `fib` can theoretically compute the value for any `bigint` value. Practically though, because `bigint` internally uses heap you might be out of system memory eventually which will crash your process. 83 | 84 | ## Final Program 85 | 86 | ```ts 87 | import {QIO, defaultRuntime} from '@qio/core' 88 | 89 | const fib = QIO.lazy( 90 | (a: bigint, l: bigint = 0n, r: bigint = 1n): QIO => { 91 | if (a <= 2n) { 92 | return QIO.resolve(l + r) 93 | } 94 | 95 | return fib(a - 1n, r, l + r) 96 | } 97 | ) 98 | 99 | const program = fib(1_000_000n) // QIO 100 | defaultRuntime().unsafeExecute(program) // A 208,988 digit long number 101 | ``` 102 | -------------------------------------------------------------------------------- /packages/docs/core/fiber.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Fiber 3 | --- 4 | 5 | The `Fiber` API provides low level concurrency handles, that helps in writing **asynchronous** programs which can be **paused** or **aborted**. 6 | 7 | `Fiber` is represented with two type params viz. 8 | 9 | 1. `A` The type of the success value that will be emitted once the Fiber is completely evaluated. 10 | 2. `E` The error type that can be emitted in case of failure. 11 | 12 | ```ts 13 | interface Fiber { 14 | join: QIO 15 | abort: QIO 16 | } 17 | ``` 18 | 19 | ## Use case 20 | 21 | Consider running the following `program`: 22 | 23 | ```ts 24 | import {QIO} from '@qio/core' 25 | 26 | const putStrLn = QIO.encase(console.log) 27 | 28 | const A = putStrLn('A').delay(1000) // QIO 29 | const B = putStrLn('B') // QIO 30 | 31 | const program = A.and(B) 32 | ``` 33 | 34 | **Output** 35 | 36 | ```bash 37 | A 38 | B 39 | ``` 40 | 41 | `A` is printed first and then `B` is printed. This guarantee is provided by the `and` operator. 42 | 43 | To make sure that the computation continues with `B` and doesn't wait for `A` to complete we can use the `fork()` operator on `A`. 44 | 45 | ```diff 46 | import {QIO} from '@qio/core' 47 | 48 | const putStrLn = QIO.encase(console.log) 49 | 50 | - const A = putStrLn('A').delay(1000) // QIO 51 | + const A = putStrLn('A').delay(1000).fork() // QIO> 52 | const B = putStrLn('B') // QIO 53 | 54 | const program = A.and(B) 55 | ``` 56 | 57 | **Output** 58 | 59 | ```bash 60 | B 61 | A 62 | ``` 63 | 64 | Calling `fork()` makes sure that `A` runs in its own Fiber, asynchronously and moves on to computing `B` as soon as possible. That way `B` is printed first and then after `1000ms`, `A` is printed. 65 | 66 | ### `F.abort` 67 | 68 | ```diff 69 | import {QIO} from '@qio/core' 70 | 71 | const putStrLn = QIO.encase(console.log) 72 | 73 | - const A = putStrLn('A').delay(1000).fork() 74 | + const A = putStrLn('A').delay(1000).fork().chain(F => F.abort.delay(100)) 75 | const B = putStrLn('B') 76 | 77 | const program = A.and(B) 78 | ``` 79 | 80 | **Output:** 81 | 82 | ```bash 83 | B 84 | ``` 85 | 86 | `F` represents a `Fiber` and using `abort.delay(100)` we are aborting the computation after `100ms` that way `A` is never computed and thus only `B` is printed on the screen. 87 | 88 | ### `F.join` 89 | 90 | ```diff 91 | import {QIO} from '@qio/core' 92 | 93 | const putStrLn = QIO.encase(console.log) 94 | 95 | - const A = putStrLn('A').delay(1000).fork().chain(F => F.abort.delay(100)) 96 | + const A = putStrLn('A').delay(1000).fork().chain(F => F.join) 97 | const B = putStrLn('B') 98 | 99 | const program = A.and(B) 100 | ``` 101 | 102 | **Output:** 103 | 104 | ```bash 105 | A 106 | B 107 | ``` 108 | 109 | `F.join` waits for the fiber to compute its value and move on to the next. In the above case `A` is printed after `1000ms` and then immediately `B` is printed. 110 | 111 | Once the fiber is computed internally, using `join` multiple times will resolve with the same cached, value every time. 112 | -------------------------------------------------------------------------------- /packages/fs/__tests__/FS.test.ts: -------------------------------------------------------------------------------- 1 | import {QIO, testRuntime} from '@qio/core' 2 | import {deepStrictEqual} from 'assert' 3 | import {assert, spy} from 'chai' 4 | 5 | import {FS} from '../lib/FS' 6 | 7 | describe('fs', () => { 8 | context('mock', () => { 9 | describe('readFile', () => { 10 | it('should read the file', () => { 11 | const actual = testRuntime().unsafeExecuteSync( 12 | FS.readFile('./hello.txt').provide({ 13 | fs: { 14 | readFile: (path) => QIO.resolve(Buffer.from('DATA:' + path)), 15 | }, 16 | }) 17 | ) 18 | const expected = Buffer.from('DATA:./hello.txt') 19 | assert.deepStrictEqual(actual, expected) 20 | }) 21 | }) 22 | 23 | describe('open', () => { 24 | it('should open the file', () => { 25 | const fsOpen = spy(() => QIO.resolve(10)) 26 | testRuntime().unsafeExecuteSync( 27 | FS.open('./hello.txt', '+w').provide({ 28 | fs: { 29 | open: fsOpen, 30 | }, 31 | }) 32 | ) 33 | fsOpen.should.be.called.with('./hello.txt', '+w') 34 | }) 35 | 36 | it('should return fd', () => { 37 | const actual = testRuntime().unsafeExecuteSync( 38 | FS.open('./hello.txt', '+w').provide({ 39 | fs: { 40 | open: () => QIO.resolve(10), 41 | }, 42 | }) 43 | ) 44 | const expected = 10 45 | assert.deepStrictEqual(actual, expected) 46 | }) 47 | }) 48 | 49 | describe('close', () => { 50 | it('should close with fd', () => { 51 | const closeSpy = spy() 52 | testRuntime().unsafeExecuteSync( 53 | FS.open('./hello.txt', '+w') 54 | .chain((_) => FS.close(_)) 55 | .provide({ 56 | fs: { 57 | close: QIO.encase(closeSpy), 58 | open: () => QIO.resolve(10), 59 | }, 60 | }) 61 | ) 62 | 63 | closeSpy.should.be.called.with(10) 64 | }) 65 | 66 | it('should fail on close()', () => { 67 | const actual = testRuntime().unsafeExecuteSync( 68 | FS.open('./hello.txt', '+w') 69 | .chain((_) => FS.close(_)) 70 | .provide({ 71 | fs: { 72 | close: () => QIO.reject(new Error('INVALID_FILE')), 73 | open: () => QIO.resolve(10), 74 | }, 75 | }) 76 | ) 77 | 78 | deepStrictEqual(actual, new Error('INVALID_FILE')) 79 | }) 80 | }) 81 | 82 | describe('writeFile', () => { 83 | it('should be able to write', () => { 84 | const writeFile = spy() 85 | testRuntime().unsafeExecuteSync( 86 | FS.open('data.txt', 'w') 87 | .chain((H) => FS.writeFile(H, 'DATA')) 88 | .provide({ 89 | fs: { 90 | open: () => QIO.resolve(10), 91 | writeFile: QIO.encase(writeFile), 92 | }, 93 | }) 94 | ) 95 | 96 | writeFile.should.be.called.with(10, 'DATA') 97 | }) 98 | }) 99 | }) 100 | }) 101 | -------------------------------------------------------------------------------- /packages/website/blog/2016-03-11-blog-post.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Blog Title 3 | author: Blog Author 4 | authorURL: http://twitter.com/ 5 | authorFBID: 100002976521003 6 | --- 7 | 8 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus elementum massa eget nulla aliquet sagittis. Proin odio tortor, vulputate ut odio in, ultrices ultricies augue. Cras ornare ultrices lorem malesuada iaculis. Etiam sit amet libero tempor, pulvinar mauris sed, sollicitudin sapien. 9 | 10 | 11 | 12 | Mauris vestibulum ullamcorper nibh, ut semper purus pulvinar ut. Donec volutpat orci sit amet mauris malesuada, non pulvinar augue aliquam. Vestibulum ultricies at urna ut suscipit. Morbi iaculis, erat at imperdiet semper, ipsum nulla sodales erat, eget tincidunt justo dui quis justo. Pellentesque dictum bibendum diam at aliquet. Sed pulvinar, dolor quis finibus ornare, eros odio facilisis erat, eu rhoncus nunc dui sed ex. Nunc gravida dui massa, sed ornare arcu tincidunt sit amet. Maecenas efficitur sapien neque, a laoreet libero feugiat ut. 13 | 14 | Nulla facilisi. Maecenas sodales nec purus eget posuere. Sed sapien quam, pretium a risus in, porttitor dapibus erat. Sed sit amet fringilla ipsum, eget iaculis augue. Integer sollicitudin tortor quis ultricies aliquam. Suspendisse fringilla nunc in tellus cursus, at placerat tellus scelerisque. Sed tempus elit a sollicitudin rhoncus. Nulla facilisi. Morbi nec dolor dolor. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Cras et aliquet lectus. Pellentesque sit amet eros nisi. Quisque ac sapien in sapien congue accumsan. Nullam in posuere ante. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin lacinia leo a nibh fringilla pharetra. 15 | 16 | Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin venenatis lectus dui, vel ultrices ante bibendum hendrerit. Aenean egestas feugiat dui id hendrerit. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Curabitur in tellus laoreet, eleifend nunc id, viverra leo. Proin vulputate non dolor vel vulputate. Curabitur pretium lobortis felis, sit amet finibus lorem suscipit ut. Sed non mollis risus. Duis sagittis, mi in euismod tincidunt, nunc mauris vestibulum urna, at euismod est elit quis erat. Phasellus accumsan vitae neque eu placerat. In elementum arcu nec tellus imperdiet, eget maximus nulla sodales. Curabitur eu sapien eget nisl sodales fermentum. 17 | 18 | Phasellus pulvinar ex id commodo imperdiet. Praesent odio nibh, sollicitudin sit amet faucibus id, placerat at metus. Donec vitae eros vitae tortor hendrerit finibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Quisque vitae purus dolor. Duis suscipit ac nulla et finibus. Phasellus ac sem sed dui dictum gravida. Phasellus eleifend vestibulum facilisis. Integer pharetra nec enim vitae mattis. Duis auctor, lectus quis condimentum bibendum, nunc dolor aliquam massa, id bibendum orci velit quis magna. Ut volutpat nulla nunc, sed interdum magna condimentum non. Sed urna metus, scelerisque vitae consectetur a, feugiat quis magna. Donec dignissim ornare nisl, eget tempor risus malesuada quis. 19 | -------------------------------------------------------------------------------- /packages/website/blog/2017-04-10-blog-post-two.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: New Blog Post 3 | author: Blog Author 4 | authorURL: http://twitter.com/ 5 | authorFBID: 100002976521003 6 | --- 7 | 8 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus elementum massa eget nulla aliquet sagittis. Proin odio tortor, vulputate ut odio in, ultrices ultricies augue. Cras ornare ultrices lorem malesuada iaculis. Etiam sit amet libero tempor, pulvinar mauris sed, sollicitudin sapien. 9 | 10 | 11 | 12 | Mauris vestibulum ullamcorper nibh, ut semper purus pulvinar ut. Donec volutpat orci sit amet mauris malesuada, non pulvinar augue aliquam. Vestibulum ultricies at urna ut suscipit. Morbi iaculis, erat at imperdiet semper, ipsum nulla sodales erat, eget tincidunt justo dui quis justo. Pellentesque dictum bibendum diam at aliquet. Sed pulvinar, dolor quis finibus ornare, eros odio facilisis erat, eu rhoncus nunc dui sed ex. Nunc gravida dui massa, sed ornare arcu tincidunt sit amet. Maecenas efficitur sapien neque, a laoreet libero feugiat ut. 13 | 14 | Nulla facilisi. Maecenas sodales nec purus eget posuere. Sed sapien quam, pretium a risus in, porttitor dapibus erat. Sed sit amet fringilla ipsum, eget iaculis augue. Integer sollicitudin tortor quis ultricies aliquam. Suspendisse fringilla nunc in tellus cursus, at placerat tellus scelerisque. Sed tempus elit a sollicitudin rhoncus. Nulla facilisi. Morbi nec dolor dolor. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Cras et aliquet lectus. Pellentesque sit amet eros nisi. Quisque ac sapien in sapien congue accumsan. Nullam in posuere ante. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin lacinia leo a nibh fringilla pharetra. 15 | 16 | Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Proin venenatis lectus dui, vel ultrices ante bibendum hendrerit. Aenean egestas feugiat dui id hendrerit. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Curabitur in tellus laoreet, eleifend nunc id, viverra leo. Proin vulputate non dolor vel vulputate. Curabitur pretium lobortis felis, sit amet finibus lorem suscipit ut. Sed non mollis risus. Duis sagittis, mi in euismod tincidunt, nunc mauris vestibulum urna, at euismod est elit quis erat. Phasellus accumsan vitae neque eu placerat. In elementum arcu nec tellus imperdiet, eget maximus nulla sodales. Curabitur eu sapien eget nisl sodales fermentum. 17 | 18 | Phasellus pulvinar ex id commodo imperdiet. Praesent odio nibh, sollicitudin sit amet faucibus id, placerat at metus. Donec vitae eros vitae tortor hendrerit finibus. Interdum et malesuada fames ac ante ipsum primis in faucibus. Quisque vitae purus dolor. Duis suscipit ac nulla et finibus. Phasellus ac sem sed dui dictum gravida. Phasellus eleifend vestibulum facilisis. Integer pharetra nec enim vitae mattis. Duis auctor, lectus quis condimentum bibendum, nunc dolor aliquam massa, id bibendum orci velit quis magna. Ut volutpat nulla nunc, sed interdum magna condimentum non. Sed urna metus, scelerisque vitae consectetur a, feugiat quis magna. Donec dignissim ornare nisl, eget tempor risus malesuada quis. 19 | -------------------------------------------------------------------------------- /packages/docs/core/environment.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Environment 3 | --- 4 | 5 | By default any QIO instance would not need any env. This can be customized based on what the program needs to perform. For example, if a program needs to read a `config` and print out the `port` it could do something like this: 6 | 7 | ## Config 8 | 9 | Say we already have a `Config` interface, with only one property — 10 | 11 | ```ts 12 | + interface Config { 13 | + port: number 14 | + } 15 | ``` 16 | 17 | ## ConfigEnv 18 | 19 | Create an Environment that returns an instance of `Config` 20 | 21 | ```ts 22 | interface Config { 23 | port: number 24 | } 25 | + 26 | + interface ConfigEnv { 27 | + config: Config 28 | + } 29 | ``` 30 | 31 | ## getPort() 32 | 33 | We add `getPort` which picks the `port` and `putStrLn` which is a wrapper over `console.log` to make it pure. 34 | 35 | ```ts 36 | + import {QIO} from '@qio/core' 37 | interface Config { 38 | port: number 39 | } 40 | interface ConfigEnv { 41 | config: Config 42 | } 43 | + const getPort = QIO.access((config: Config) => config.port) 44 | + const putStrLn = QIO.encase(console) 45 | ``` 46 | 47 | ## Create program 48 | 49 | Using the [chain] operator one can now chain them one after the other — 50 | 51 | [chain]: api/classes/qio.md#chain 52 | 53 | ```ts 54 | import {QIO} from '@qio/core' 55 | interface Config { 56 | port: number 57 | } 58 | interface ConfigEnv { 59 | config: Config 60 | } 61 | const getPort = QIO.access((config: Config) => config.port) 62 | const putStrLn = QIO.encase((message: string) => console.log(message)) 63 | 64 | + const program = getPort().chain(putStrLn) 65 | ``` 66 | 67 | ## Provide Env 68 | 69 | You can provide the env directly to a QIO instance without executing it using the [provide] method. 70 | 71 | ```ts 72 | import {QIO} from '@qio/core' 73 | + 74 | + import config from './config' 75 | interface Config { 76 | port: number 77 | } 78 | interface ConfigEnv { 79 | config: Config 80 | } 81 | const getPort = QIO.access((config: Config) => config.port) 82 | const putStrLn = QIO.encase((message: string) => console.log(message)) 83 | 84 | const program = getPort().chain(putStrLn) 85 | 86 | + const env = { 87 | + config: config 88 | + } 89 | + const program0 = program.provide(env) 90 | ``` 91 | 92 | [provide]: api/classes/qio.md#provide 93 | 94 | ### Running the program 95 | 96 | Running the program can be done by using the runtime. 97 | 98 | ```ts 99 | - import {QIO} from '@qio/core' 100 | + import {QIO, defaultRuntime} from '@qio/core' 101 | 102 | import config from 'config' 103 | interface Config { 104 | port: number 105 | } 106 | interface ConfigEnv { 107 | config: Config 108 | } 109 | const getPort = QIO.access((config: Config) => config.port) 110 | const putStrLn = QIO.encase((message: string) => console.log(message)) 111 | 112 | const program = getPort().chain(putStrLn) 113 | 114 | const env = { 115 | config: config 116 | } 117 | const program0 = program.provide(env) 118 | + defaultRuntime().unsafeExecute(program0) 119 | ``` 120 | 121 | [provide]: api/classes/qio.md#provide 122 | 123 | ### Next Steps 124 | 125 | Checkout a fully functional example [here](https://github.com/tusharmath/qio/tree/master/packages/example). 126 | -------------------------------------------------------------------------------- /packages/docs/concepts/non-determinism.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Non Determinism 3 | --- 4 | 5 | Another typical problem that a real world program needs to deal with is non-determinism. When the giving the same input to a program produces different results, its called as non-determinism, for eg: 6 | 7 | ```ts 8 | const elapsedTime = (dob: Date): number => { 9 | const today = new Date() 10 | return today.getTime() - dob.getTime() 11 | } 12 | ``` 13 | 14 | The function `calculateAge` when given the same input will produce different results as time passes. The program doesn't cause any [side-effects] but is non-deterministic. 15 | 16 | [side-effects]: side-effects 17 | 18 | ## Problems 19 | 20 | The problem with non-deterministic functions is that they are extremely hard to test. For eg. to test `elapsedTime` we would need to inject the today's date from outside: 21 | 22 | ```ts 23 | const elapsedTime = (dob: Date, today: Date): number => { 24 | return today.getTime() - dob.getTime() 25 | } 26 | ``` 27 | 28 | This solves the problem locally but it also shifts it up to its caller: 29 | 30 | ```ts 31 | const elapsedTime = (dob: Date, today: Date): number => { 32 | return today.getTime() - dob.getTime() 33 | } 34 | 35 | const main = (today: Date): number => { 36 | const dob = new Date(1980, 1, 1) 37 | return elapsedTime(dob, today) 38 | } 39 | ``` 40 | 41 | This isn't scalable because as more dependencies are added or removed, every function between the consumer of the dependency and the root function will have change their signature. 42 | 43 | ```ts 44 | const foo = (math: Math): number => { 45 | // ... 46 | } 47 | 48 | const main = (today: Date, math: Math): number => { 49 | const dob = new Date(1980, 1, 1) 50 | const bar = foo(math) 51 | return elapsedTime(dob, today) + bar 52 | } 53 | ``` 54 | 55 | ## Solving using QIO 56 | 57 | Using QIO, we can solve this problem in a more manageable way. 58 | 59 | ### Define Environment Dependencies 60 | 61 | First we create wrapper types on top of each dependency. It's a simple interface that will provide access to those environments. 62 | 63 | ```ts 64 | interface DateEnv = { 65 | Date: Date 66 | } 67 | 68 | interface MathEnv = { 69 | Math: Math 70 | } 71 | ``` 72 | 73 | ### Access the environment 74 | 75 | We change the implementation for `elapsedTime` to use the `DateEnv`: 76 | 77 | ```ts 78 | const elapsedTime = (dob: Date): QIO => { 79 | return QIO.access((_: DateEnv) => _.Date().getTime() - dob) 80 | } 81 | 82 | const foo = (): QIO => { 83 | // ... 84 | } 85 | ``` 86 | 87 | The return type of both the functions now contain information about the specific environment they need. 88 | 89 | ### Composing the environment 90 | 91 | ```ts 92 | const main = (): QIO => { 93 | const dob = new Date(1980, 1, 1) 94 | 95 | return elapsedTime(dob).zipWith(foo(), (a, b) => a + b) 96 | } 97 | ``` 98 | 99 | Combining `elapsedTime` with `foo` using the `zipWith` operator ensure that the final environment that is needed is of the type of `DateEnv & MathEnv`. 100 | 101 | Adding more external environment dependencies doesn't require you to change the signature of function arguments. 102 | 103 | QIO will automatically compose these environments for you so that you can provide it the final env at the time of evaluation. 104 | 105 | ```ts 106 | main() // QIO 107 | main().provide({Math, Date}) // QIO 108 | ``` 109 | -------------------------------------------------------------------------------- /packages/core/lib/main/Managed.ts: -------------------------------------------------------------------------------- 1 | import {List} from 'standard-data-structures' 2 | 3 | import {QIO} from './QIO' 4 | import {Reservation} from './Reservation' 5 | 6 | /** 7 | * Is a special data structures that encapsulates the acquisition and the release of a resource. 8 | * 9 | * **Example:** 10 | * ```ts 11 | * import * as fs from 'fs' 12 | * import {QIO, Managed} from '@qio/core' 13 | * 14 | * // Utility Functions 15 | * const fsOpen = (name: string) => QIO.node(cb => fs.open(name, cb)) 16 | * const fsClose = (fd: number) => QIO.try(() => fs.close(fd)) 17 | * const fsWrite = (fd: number, text: string) => QIO.node((cb) => fs.write(fd, text, cb)) 18 | * 19 | * // Create a managed File Descriptor resource 20 | * const fdManaged = (name: string) => Managed.make(fsOpen(name), fsClose) 21 | * 22 | * // Use the `fdManaged` to write the file. 23 | * fdManaged('./data.json').use(fd => fsWrite(fd, 'HI')) 24 | * ``` 25 | * 26 | * In the above example as soon as the `fsWrite` is completed the file descriptor is automatically released. 27 | */ 28 | export class Managed { 29 | public static make( 30 | acquire: QIO, 31 | release: (a: A1) => QIO 32 | ): Managed { 33 | return Managed.of( 34 | acquire 35 | .map((a1) => Reservation.of(QIO.resolve(a1).addEnv(), release(a1))) 36 | .addEnv() 37 | ) 38 | } 39 | 40 | public static of( 41 | reservation: QIO, E1, R1> 42 | ): Managed { 43 | return new Managed(reservation) 44 | } 45 | 46 | public static zip( 47 | managed: Array> 48 | ): Managed { 49 | return managed 50 | .reduce( 51 | (a: Managed, E1, R1>, b: Managed) => 52 | a.zipWith(b, (x, y) => x.prepend(y)), 53 | Managed.make(QIO.resolve(List.empty()).addEnv(), QIO.void) 54 | ) 55 | .map((_) => _.asArray.reverse()) 56 | } 57 | 58 | private constructor( 59 | private readonly reservation: QIO, E1, R1> 60 | ) {} 61 | 62 | public chain( 63 | fn: (a: A1) => Managed 64 | ): Managed { 65 | return Managed.of( 66 | this.reservation.chain((r1) => 67 | r1.acquire.chain((a1) => 68 | fn(a1).reservation.map((r2) => { 69 | const acquire = r2.acquire 70 | const release = r2.release.and(r1.release) 71 | 72 | return Reservation.of(acquire, release) 73 | }) 74 | ) 75 | ) 76 | ) 77 | } 78 | 79 | public map(fn: (a: A1) => A2): Managed { 80 | return Managed.of( 81 | this.reservation.map((r1) => 82 | Reservation.of(r1.acquire.map(fn), r1.release) 83 | ) 84 | ) 85 | } 86 | 87 | public use( 88 | fn: (a: A1) => QIO 89 | ): QIO { 90 | return this.reservation.chain((R) => R.acquire.bracket_(R.release)(fn)) 91 | } 92 | 93 | public use_(io: QIO): QIO { 94 | return this.use(() => io) 95 | } 96 | 97 | public zipWith( 98 | that: Managed, 99 | fn: (a1: A1, a2: A2) => X 100 | ): Managed { 101 | return this.chain((a1) => that.map((a2) => fn(a1, a2))) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /packages/core/lib/main/Queue.ts: -------------------------------------------------------------------------------- 1 | import {debug} from 'debug' 2 | import {DoublyLinkedList, List, Option} from 'standard-data-structures' 3 | 4 | import {IDGenerator} from '../internals/IDGenerator' 5 | 6 | import {Await} from './Await' 7 | import {QIO} from './QIO' 8 | 9 | const QUEUE_ID = new IDGenerator() 10 | const D = (scope: string, f: unknown, ...t: unknown[]) => 11 | debug('qio:queue')(scope, f, ...t) 12 | 13 | const consume = (T: DoublyLinkedList): T[] => { 14 | const R = new Array() 15 | while (T.length !== 0) { 16 | const item = T.shift() 17 | if (Option.isSome(item)) { 18 | R.push(item.value) 19 | } 20 | } 21 | 22 | return R 23 | } 24 | 25 | /** 26 | * Queue Data Structure 27 | */ 28 | export class Queue { 29 | /** 30 | * Creates a new bounded Queue 31 | */ 32 | public static bounded(capacity: number): QIO> { 33 | return QIO.lift(() => new Queue(capacity)) 34 | } 35 | /** 36 | * Creates a queue which is theoretically unbounded. 37 | */ 38 | public static unbounded(): QIO> { 39 | return Queue.bounded(Number.MAX_SAFE_INTEGER) 40 | } 41 | private readonly id = QUEUE_ID.create() 42 | private readonly O = DoublyLinkedList.of>() 43 | private readonly Q = DoublyLinkedList.of() 44 | private readonly T = DoublyLinkedList.of>() 45 | private constructor(public readonly capacity: number) {} 46 | /** 47 | * Returns the Queue as an array 48 | */ 49 | public get asArray(): QIO { 50 | return QIO.lift(() => this.Q.asArray) 51 | } 52 | /** 53 | * Returns the number of elements in the queue 54 | */ 55 | public get length(): QIO { 56 | return QIO.lift(() => this.Q.length) 57 | } 58 | /** 59 | * Pulls an item from the queue 60 | */ 61 | public get take(): QIO { 62 | return QIO.tryM(() => { 63 | D('%i this.take() %O', this.id, this.stat) 64 | 65 | const sz = this.Q.shift() 66 | 67 | if (Option.isSome(sz)) { 68 | return this.resolvePendingOffers().const(sz.value) 69 | } 70 | 71 | return Await.of().chain( 72 | QIO.encaseM((awt) => { 73 | this.T.add(awt) 74 | 75 | return awt.get 76 | }) 77 | ) 78 | }).do(this.resolvePendingOffers()) 79 | } 80 | 81 | public get waitForSpace(): QIO { 82 | return Await.of().encaseM((AWT) => { 83 | this.O.add(AWT) 84 | 85 | return AWT.get 86 | }) 87 | } 88 | private get stat(): {pOffers: number; pTakes: number; queue: number} { 89 | return { 90 | pOffers: this.O.length, 91 | pTakes: this.T.length, 92 | queue: this.Q.length, 93 | } 94 | } 95 | /** 96 | * Inserts an item into the queue 97 | */ 98 | public offer(a: A): QIO { 99 | return QIO.tryM( 100 | (): QIO => { 101 | D('%i this.offer(%O) %O', this.id, a, this.stat) 102 | if (this.Q.length === this.capacity) { 103 | return this.waitForSpace.and(this.offer(a)) 104 | } 105 | 106 | this.Q.add(a) 107 | 108 | return this.resolvePendingTakes() 109 | } 110 | ) 111 | } 112 | 113 | /** 114 | * Adds all the provided items into the queue 115 | */ 116 | public offerAll(...a: A[]): QIO { 117 | return QIO.seq(a.map((_) => this.offer(_))).void 118 | } 119 | /** 120 | * Resolves after `n` items are available in the queue. 121 | */ 122 | public takeN(n: number): QIO { 123 | const itar = (i: number, list: List): QIO> => 124 | QIO.if0()( 125 | () => i === n, 126 | () => QIO.resolve(list), 127 | () => this.take.chain((_) => itar(i + 1, list.prepend(_))) 128 | ) 129 | 130 | return itar(0, List.empty()).map((_) => _.asArray.reverse()) 131 | } 132 | private resolvePendingOffers(): QIO { 133 | return QIO.seq(consume(this.O).map((_) => _.setTo(true))).void 134 | } 135 | private resolvePendingTakes(): QIO { 136 | if (this.T.length > 0) { 137 | const aa = this.Q.shift() 138 | if (Option.isSome(aa)) { 139 | return QIO.seq(consume(this.T).map((_) => _.setTo(aa.value))).void 140 | } 141 | } 142 | 143 | return QIO.void() 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /packages/website/siteConfig.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | // See https://docusaurus.io/docs/site-config for all the possible 9 | // site configuration options. 10 | 11 | const projectName = 'qio' 12 | 13 | // List of projects/orgs using your project for the users page. 14 | const users = [ 15 | { 16 | // caption: 'User1', 17 | // You will need to prepend the image path with your baseUrl 18 | // if it is not '/', like: '/${name}/img/image.jpg'. 19 | // image: `/${projectName}/${webSiteName}/img/undraw_open_source.svg`, 20 | // infoLink: 'https://www.facebook.com', 21 | // pinned: true 22 | } 23 | ] 24 | 25 | const siteConfig = { 26 | title: 'QIO', // Title for your website. 27 | tagline: 'A high performance effect management library.', 28 | url: `https://${projectName}.netlify.com`, // Your website URL 29 | baseUrl: `/`, // Base URL for your project */ 30 | // For github.io type URLs, you would set the url and baseUrl like: 31 | // url: 'https://facebook.github.io', 32 | // baseUrl: '/${name}/', 33 | 34 | // Used for publishing and more 35 | projectName: `${projectName}`, 36 | organizationName: 'tusharmath', 37 | // For top-level user or org sites, the organization is still the same. 38 | // e.g., for the https://JoelMarcey.github.io site, it would be set like... 39 | // organizationName: 'JoelMarcey' 40 | 41 | // For no header links in the top nav bar -> headerLinks: [], 42 | headerLinks: [ 43 | // {doc: 'getting-started', label: 'Getting Started'}, 44 | // {doc: 'doc4', label: 'API'}, 45 | // {page: 'help', label: 'Help'}, 46 | // {doc: 'concepts/introduction', label: 'Concepts'}, 47 | {doc: 'core/installation', label: 'Getting Started'}, 48 | {doc: 'core/qio', label: 'Entities'}, 49 | {doc: 'api/globals', label: 'API'}, 50 | {doc: 'others/ecosystem', label: 'Ecosystem'}, 51 | {doc: 'comparison/benchmarks', label: 'Benchmarks'}, 52 | {href: 'https://github.com/tusharmath/qio', label: 'Github'} 53 | ], 54 | 55 | // If you have users set above, you add it here: 56 | users, 57 | 58 | /* path to images for header/footer */ 59 | // headerIcon: 'img/favicon.ico', 60 | footerIcon: 'img/favicon.ico', 61 | favicon: 'img/favicon.ico', 62 | 63 | /* Colors for website */ 64 | colors: { 65 | primaryColor: '#444bb1', 66 | secondaryColor: '#0a3e2f' 67 | }, 68 | 69 | /* Custom fonts for website */ 70 | /* 71 | fonts: { 72 | myFont: [ 73 | "Times New Roman", 74 | "Serif" 75 | ], 76 | myOtherFont: [ 77 | "-apple-system", 78 | "system-ui" 79 | ] 80 | }, 81 | */ 82 | 83 | fonts: { 84 | defaultFont: ['Lora', 'Serif'], 85 | monospaceFont: ['Fira Code', 'monospace'] 86 | }, 87 | 88 | // This copyright info is used in /core/Footer.js and blog RSS/Atom feeds. 89 | copyright: `Copyright © ${new Date().getFullYear()} tusharmath.com`, 90 | 91 | highlight: { 92 | // Highlight.js theme to use for syntax highlighting in code blocks. 93 | theme: 'default' 94 | }, 95 | 96 | // Add custom scripts here that would be placed in