diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml new file mode 100644 index 0000000..545ae30 --- /dev/null +++ b/.github/workflows/npm-publish.yml @@ -0,0 +1,20 @@ +name: NPM publish package + +on: + release: + types: [created] + +jobs: + publish-npm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + registry-url: https://registry.npmjs.org/ + - run: npm ci + - run: npm run build + - run: npm run publish-npm -- --provenance + env: + NODE_AUTH_TOKEN: ${{secrets.npm_token}} diff --git a/.github/workflows/quality-check.yml b/.github/workflows/quality-check.yml new file mode 100644 index 0000000..0fbd21f --- /dev/null +++ b/.github/workflows/quality-check.yml @@ -0,0 +1,22 @@ +name: Quality check + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'npm' + - run: npm ci + - run: npm run compile + - run: npm run lint + - run: npm run test diff --git a/package.json b/package.json index 550da1b..fed5532 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "name": "@arnonym/result", - "version": "1.0.2", + "version": "1.0.3", "description": "Result type for TypeScript inspired by Rust", "main": "dist/index.js", "types": "dist/index.d.ts", "scripts": { - "build": "rm -rf dist; npm run check && vite build --mode esm && vite build --mode cjs", - "publish-npm": "npm run build && npm publish --access public", + "build": "rm -rf dist; vite build --mode esm && vite build --mode cjs", + "publish-npm": "npm publish --access public", "compile": "tsc --noEmit", "test": "jest", "lint": "eslint", diff --git a/src/index.ts b/src/index.ts index 8f4b755..9c82208 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,2 @@ -export { Result, Match, Ok, Err } from './result'; +export { Result, type Match, type Ok, type Err } from './result'; export { pipe } from './pipe'; diff --git a/src/result.test.ts b/src/result.test.ts index 83667d0..38e2a58 100644 --- a/src/result.test.ts +++ b/src/result.test.ts @@ -214,7 +214,41 @@ describe('result', () => { const val = Result.err(-1); expect(() => { val.unwrap(); - }).toThrow(); + }).toThrow("-1"); + }); + }); + + describe('expect', () => { + test('should "expect" ok', () => { + const val = Result.ok(3); + const val2 = val.expect("You did it wrong!"); + expect(val2).toBe(3); + }); + test('should not "expect" err but throw with string', () => { + const val = Result.err(-1); + expect(() => { + val.expect("You did it wrong!"); + }).toThrow(new Error("You did it wrong!")); + }); + test('should not "expect" err but throw with function', () => { + const val = Result.err(-1); + expect(() => { + val.expect(e => `You did it wrong: ${e}`); + }).toThrow(new Error("You did it wrong: -1")); + }); + }); + + describe('unwrapErr', () => { + test('should "unwrapErr" err', () => { + const val = Result.err(3); + const val2 = val.unwrapErr(); + expect(val2).toBe(3); + }); + test('should not "unwrapErr" err but throw', () => { + const val = Result.ok(-2); + expect(() => { + val.unwrapErr(); + }).toThrow("Tried to unwrapErr on value: -2"); }); }); @@ -441,7 +475,7 @@ describe('result', () => { }); test('assertOk on error should throw', () => { const result = Result.err(3); - expect(() => Result.assertOk(result)).toThrow(); + expect(() => Result.assertOk(result)).toThrow("Expected Ok, got Err: 3"); }); test('assertErr on err', () => { const result = Result.err(3); @@ -450,7 +484,7 @@ describe('result', () => { }); test('assertOk on error should throw', () => { const result = Result.ok(3); - expect(() => Result.assertErr(result)).toThrow(); + expect(() => Result.assertErr(result)).toThrow("Expected Err, got Ok: 3"); }); }); diff --git a/src/result.ts b/src/result.ts index 7566a96..be5f7fc 100644 --- a/src/result.ts +++ b/src/result.ts @@ -17,6 +17,8 @@ type IncludesType, S> = { export type NeverIfExistingInUnion> = IncludesType extends true ? never : T; +type ExpectMessage = string | ((err: E) => string); + interface Functions { isOk: () => this is Ok; isErr: () => this is Err; @@ -24,6 +26,8 @@ interface Functions { andThen: (fn: (value: O) => Result) => Result; mapErr: (fn: (err: E) => NE) => Result; unwrap: () => O; + unwrapErr: () => E; + expect: (message: ExpectMessage) => O; unwrapOr: (def: R1) => O | R1; unwrapOrElse: (def: (err: E) => R1) => O | R1; or: (def: R1) => Result; @@ -81,7 +85,24 @@ function unwrap(data: Result): O { if (data.isOk()) { return data.value; } - throw new Error(`Tried to unwrap error: ${data.err}`); + throw data.err; +} + +function unwrapErr(data: Result): E { + if (data.isErr()) { + return data.err; + } + throw new Error(`Tried to unwrapErr on value: ${data.value}`); +} + +function expect(message: ExpectMessage): (data: Result) => O { + return data => { + if (data.isOk()) { + return data.value; + } + const m = typeof message === 'function' ? message(data.err) : message; + throw new Error(m); + } } function unwrapOr(def: R1): (data: Result) => O | R1 { @@ -139,6 +160,14 @@ class InternalResult implements Functions { return unwrap(this as unknown as Result); } + unwrapErr() { + return unwrapErr(this as unknown as Result); + } + + expect(message: ExpectMessage) { + return expect(message)(this as unknown as Result); + } + unwrapOr(def: R1) { return unwrapOr(def)(this as unknown as Result); }