Skip to content

Commit 4be4518

Browse files
committed
Publish koka from koka-stack repository
1 parent 8323c7d commit 4be4518

File tree

5 files changed

+88
-15
lines changed

5 files changed

+88
-15
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Koka - Lightweight 3kB Effect-TS alternative library based on Algebraic Effects
22

3+
** Warning: This library is in early development and may change significantly. Do not use in production yet. **
4+
35
Koka is a minimal yet powerful effects library for TypeScript that provides structured error handling, context management, and async operations in a composable, type-safe manner.
46

57
Inspired by algebraic effects from [koka-lang](https://github.com/koka-lang/koka), it offers a pragmatic alternative to traditional error handling. Compared to comprehensive solutions like [Effect-TS](https://github.com/Effect-TS/effect), Koka focuses on delivering essential effect management with minimal overhead.

__tests__/koka.test.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,54 @@ describe('Eff.try/catch', () => {
103103
error: 'error',
104104
})
105105
})
106+
107+
it('should handle multiple catches', () => {
108+
function* test() {
109+
yield* Eff.err('FirstError').throw('first error')
110+
yield* Eff.err('SecondError').throw('second error')
111+
return 'should not reach here'
112+
}
113+
114+
const program = Eff.try(test()).catch({
115+
FirstError: (error) => `Caught first: ${error}`,
116+
SecondError: (error) => `Caught second: ${error}`,
117+
})
118+
119+
const result = Eff.run(program)
120+
expect(result).toBe('Caught first: first error')
121+
})
122+
123+
it('should handle nested try/catch', () => {
124+
function* inner() {
125+
yield* Eff.err('InnerError').throw('inner error')
126+
return 'should not reach here'
127+
}
128+
129+
function* outer() {
130+
return yield* inner()
131+
}
132+
133+
const result = Eff.run(
134+
Eff.try(outer()).catch({
135+
InnerError: (error) => `Caught inner: ${error}`,
136+
}),
137+
)
138+
expect(result).toBe('Caught inner: inner error')
139+
})
140+
141+
// it('should catch promise via async handler', async () => {
142+
// function* test() {
143+
// const value = yield* Eff.await(Promise.reject('Async error'))
144+
// return value
145+
// }
146+
147+
// const program = Eff.try(test()).catch({
148+
// async: (promise) => `Caught async: ${promise}`,
149+
// })
150+
151+
// const result = Eff.run(program)
152+
// expect(result).toBe('Caught async: Async error')
153+
// })
106154
})
107155

108156
describe('Eff.run', () => {
@@ -299,4 +347,4 @@ describe('helpers', () => {
299347
expect(isGenerator(gen())).toBe(true)
300348
expect(isGenerator(notGen())).toBe(false)
301349
})
302-
})
350+
})

cjs/koka.js

Lines changed: 9 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cjs/koka.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/koka.ts

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ type ToHandler<Effect> = Effect extends Err<infer Name, infer U>
3434
? Record<Name, (error: U) => unknown>
3535
: Effect extends Ctx<infer Name, infer U>
3636
? Record<Name, U>
37+
: Effect extends Async
38+
? {
39+
async: (value: Promise<unknown>) => Promise<unknown>
40+
}
3741
: never
3842

3943
export type EffectHandlers<Effect> = UnionToIntersection<ToHandler<Effect>>
@@ -77,6 +81,10 @@ export const Result = {
7781

7882
type InferOkValue<T> = T extends Ok<infer U> ? U : never
7983

84+
type Prettier<T> = {
85+
[K in keyof T]: T[K]
86+
}
87+
8088
export type MaybePromise<T> = T extends Promise<any> ? T : T | Promise<T>
8189

8290
export type MaybeFunction<T> = T | (() => T)
@@ -109,7 +117,7 @@ export class Eff {
109117
}
110118
static try = <Yield extends AnyEff, Return>(input: MaybeFunction<Generator<Yield, Return>>) => {
111119
return {
112-
*catch<Handlers extends Partial<EffectHandlers<Yield>> | {}>(
120+
*catch<Handlers extends Partial<EffectHandlers<Yield>> & {}>(
113121
handlers: Handlers,
114122
): Generator<Exclude<Yield, { name: keyof Handlers }>, Return | ReturnType<ExtractFunctions<Handlers>>> {
115123
const gen = typeof input === 'function' ? input() : input
@@ -127,15 +135,21 @@ export class Eff {
127135
result = gen.next(yield effect as any)
128136
}
129137
} else if (effect.type === 'ctx') {
130-
const context = handlers[effect.name as keyof Handlers]
131-
132138
if (handlers.hasOwnProperty(effect.name)) {
139+
const context = handlers[effect.name as keyof Handlers]
133140
result = gen.next(context)
134141
} else {
135142
result = gen.next(yield effect as any)
136143
}
137-
} else {
144+
} else if (effect.type === 'async') {
145+
if ('async' in handlers) {
146+
const asyncHandler = handlers['async'] as (value: Promise<unknown>) => Promise<unknown>
147+
result = gen.next(yield asyncHandler(effect.value) as any)
148+
}
138149
result = gen.next(yield effect as any)
150+
} else {
151+
effect satisfies never
152+
throw new Error(`Unexpected effect: ${JSON.stringify(effect, null, 2)}`)
139153
}
140154
}
141155

@@ -148,11 +162,15 @@ export class Eff {
148162
static run<Return>(input: MaybeFunction<Generator<Async, Return>>): MaybePromise<Return> {
149163
const process = (result: IteratorResult<Async, Return>): MaybePromise<Return> => {
150164
if (!result.done) {
151-
const asyncEff = result.value
152-
153-
return asyncEff.value.then((value) => {
154-
return process(gen.next(value))
155-
}) as MaybePromise<Return>
165+
const effect = result.value
166+
167+
if (effect.type === 'async') {
168+
return effect.value.then((value) => {
169+
return process(gen.next(value))
170+
}) as MaybePromise<Return>
171+
} else {
172+
throw new Error(`Expected async effect, but got: ${JSON.stringify(effect, null, 2)}`)
173+
}
156174
}
157175

158176
return result.value as MaybePromise<Return>

0 commit comments

Comments
 (0)