Skip to content

Commit 6ffc0a3

Browse files
committed
fix: more cache testing and a note in readme
Signed-off-by: tunnckoCore <5038030+tunnckoCore@users.noreply.github.com>
1 parent d1f525d commit 6ffc0a3

File tree

4 files changed

+101
-23
lines changed

4 files changed

+101
-23
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Zagora
22

3-
[![ci](https://github.com/tunnckoCore/zagora/actions/workflows/ci.yml/badge.svg)](https://github.com/tunnckoCore/zagora/actions/workflows/ci.yml) <!-- COV_BADGE:START -->![coverage](https://badgen.net/badge/coverage/91.24%25/99CC09)<!-- COV_BADGE:END -->
3+
[![ci](https://github.com/tunnckoCore/zagora/actions/workflows/ci.yml/badge.svg)](https://github.com/tunnckoCore/zagora/actions/workflows/ci.yml) <!-- COV_BADGE:START -->![coverage](https://badgen.net/badge/coverage/96.13%25/99CC09)<!-- COV_BADGE:END -->
44

55
Elevate your TypeScript workflow with Zagora: a sleek, bulletproof toolkit for forging type-safe, error-proof functions and libraries that never throw. Powered by StandardSchema-compliant validators like Zod, Valibot, and Arktype, it delivers rock-solid input/output validation and richly typed errors. No routers, no network baggage — just pure, exportable functions ready to supercharge your code. The ultimate streamlined alternative to oRPC and tRPC, stripping away the network layer for unmatched type-safety, simplicity and robustness.
66

@@ -542,6 +542,7 @@ Built-in caching with custom cache adapter. Cache key includes the input, the in
542542
- if cache method throws, the process never crash - you can find the error at the standard `result.error`
543543
- failures in cache adapter will be reported as `UNKNOWN_ERROR` in `result.error` with `result.error.cause` set to the error thrown
544544
- in future this could change to be `CACHE_ERROR` with `cause`
545+
- when cache is passed through `.callable` - and has async methods, make sure to await the procedure and ignore the TypeScript warning that "you may not need await here" - you do need to await
545546

546547
```ts
547548
const cache = new Map();

src/index.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -249,10 +249,12 @@ export class Zagora<
249249
}
250250

251251
private _createProcedure<
252+
TCache,
252253
TNewContext extends TContext = TContext,
253254
TKindNames extends
254255
ResolveErrorKindNames<TErrorsMap> = ResolveErrorKindNames<TErrorsMap>,
255256
>(context?: TNewContext) {
257+
/* v8 ignore next -- @preserve */
256258
if (typeof this["~zagora"].handler !== "function") {
257259
this["~zagora"].handler = () => {};
258260
}
@@ -294,7 +296,7 @@ export class Zagora<
294296

295297
type TResult = ConditionalAsync<ReturnType<THandlerFn>, Result>;
296298

297-
type TFinalResult = TCacheAdapter extends {
299+
type TFinalResult = TCache extends {
298300
has(key: string): infer A;
299301
get(key: string): infer B;
300302
set(key: string, value: unknown): infer C;
@@ -318,13 +320,16 @@ export class Zagora<
318320
callable<
319321
TNewContext extends TContext,
320322
TKindNames extends ResolveErrorKindNames<TErrorsMap>,
321-
TNewCacheAdapter extends CacheAdapter,
322-
>(options: { context?: TNewContext; cache?: TNewCacheAdapter } = {}) {
323+
TCacheDefinitely extends CacheAdapter,
324+
TNewCacheAdapter extends TCacheDefinitely | undefined = undefined,
325+
>(options: { context?: TNewContext; cache?: TCacheDefinitely } = {}) {
323326
let za = this;
324327
if (options.cache) {
325-
za = this.cache(options.cache) as any;
328+
za = this.cache<TCacheDefinitely>(options.cache) as any;
326329
}
327330

328-
return za._createProcedure<TNewContext, TKindNames>(options.context);
331+
return za._createProcedure<TNewCacheAdapter, TNewContext, TKindNames>(
332+
options.context,
333+
);
329334
}
330335
}

src/utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ export function executeHandler(
213213
"set",
214214
);
215215
if (resp instanceof Promise) {
216+
/* v8 ignore next -- @preserve */
216217
return resp.then((resolved) =>
217218
resolved.ok ? handlerResolved : resolved,
218219
);

test/main.test.ts

Lines changed: 88 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -626,23 +626,94 @@ test("basic in-memory caching/memoization", async () => {
626626
});
627627

628628
test("cache adapter passed through `.callable` method", async () => {
629-
let called = 0;
630-
const hello = zagora()
631-
.context({ age: 10 })
632-
.input(z.string())
633-
.handler(({ context }) => {
634-
called += 1;
635-
return context.age + called;
636-
})
637-
.callable({ cache: new Map() });
629+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: ok
630+
async function fixture(
631+
withSetError = false,
632+
withGetError = false,
633+
withHasError = false,
634+
) {
635+
let called = 0;
636+
const cache = new Map();
637+
const hello = zagora()
638+
.context({ age: 10 })
639+
.input(z.string())
640+
.handler(({ context }) => {
641+
called += 1;
642+
return context.age + called;
643+
})
644+
.callable({
645+
cache: {
646+
has(key: string) {
647+
if (withHasError) {
648+
throw new Error("The has is not implemented");
649+
}
650+
return cache.has(key);
651+
},
652+
get(key: string) {
653+
if (withGetError) {
654+
throw new Error("Get method not implemented yet");
655+
}
656+
return cache.get(key);
657+
},
658+
async set(key: string, value: any) {
659+
if (withSetError) {
660+
throw new Error("Set method is not implemented");
661+
}
662+
cache.set(key, value);
663+
},
664+
},
665+
});
666+
667+
const res = await hello("foo");
668+
if (withSetError || withHasError) {
669+
expect(res.ok).toBe(false);
670+
if (!res.ok) {
671+
expect(res.error.kind).toBe("UNKNOWN_ERROR");
672+
expect(res.error.message).toContain(
673+
withSetError
674+
? "Failure in async CacheAdapter.set"
675+
: "Failure in CacheAdapter.has",
676+
);
677+
if (withSetError) {
678+
expect((res.error as any)?.cause?.message).toContain("Set method is");
679+
}
680+
if (withHasError) {
681+
expect((res.error as any)?.cause?.message).toContain(
682+
"The has is not impl",
683+
);
684+
}
685+
}
686+
687+
return;
688+
}
689+
expect(res.ok).toBe(true);
690+
expect(called, "expects to be called once").toBe(1);
691+
expect((res as any).data).toStrictEqual(11);
692+
693+
const res2 = await hello("foo");
694+
if (withGetError) {
695+
expect(res2.ok).toBe(false);
696+
if (!res2.ok) {
697+
expect(res2.error.kind).toBe("UNKNOWN_ERROR");
698+
expect(res2.error.message).toContain("Failure in CacheAdapter.get");
699+
expect((res2.error as any)?.cause?.message).toContain(
700+
"Get method not impl",
701+
);
702+
}
638703

639-
const res = hello("foo");
640-
expect(res.ok).toBe(true);
641-
expect(called, "expects to be called once").toBe(1);
642-
expect((res as any).data).toStrictEqual(11);
704+
return;
705+
}
706+
expect(res2.ok).toBe(true);
707+
expect(called, "expects to be called only once").toBe(1);
708+
expect((res2 as any).data).toStrictEqual(11);
709+
}
643710

644-
const res2 = hello("foo");
645-
expect(res2.ok).toBe(true);
646-
expect(called, "expects to be called only once").toBe(1);
647-
expect((res2 as any).data).toStrictEqual(11);
711+
fixture();
712+
fixture(true);
713+
fixture(true, true);
714+
fixture(false, true);
715+
fixture(false, true, true);
716+
fixture(true, false);
717+
fixture(true, true, false);
718+
fixture(false, false, true);
648719
});

0 commit comments

Comments
 (0)