Skip to content

Commit

Permalink
Merge pull request #12 from lishaduck/deno-kv
Browse files Browse the repository at this point in the history
Deno KV
  • Loading branch information
lishaduck authored Jan 3, 2025
2 parents d64204d + f6d5ac2 commit bbc4544
Show file tree
Hide file tree
Showing 10 changed files with 215 additions and 113 deletions.
1 change: 1 addition & 0 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"nodeModulesDir": "manual",
"license": "MIT",
"workspace": ["./packages/*"],
"unstable": ["kv"],
"imports": {
"@biomejs/biome": "npm:@biomejs/biome@^1.9.4",
"@effect/vitest": "npm:@effect/vitest@^0.13.16",
Expand Down
1 change: 1 addition & 0 deletions packages/platform-deno/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
".": "./src/mod.ts",
"./DenoContext": "./src/DenoContext.ts",
"./DenoFileSystem": "./src/DenoFileSystem.ts",
"./DenoKeyValueStore": "./src/DenoKeyValueStore.ts",
"./DenoPath": "./src/DenoPath.ts",
"./DenoRuntime": "./src/DenoRuntime.ts",
"./DenoWorker": "./src/DenoWorker.ts",
Expand Down
22 changes: 21 additions & 1 deletion packages/platform-deno/src/DenoContext.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,27 @@
/**
* This module exposes a {@linkplain Layer.Layer | layer} for Deno-powered operations.
* This module exposes a platform context {@linkplain Layer.Layer | layer} for Deno-powered applications.
* @module
*
* @example
* ```ts
* import { Path } from "@effect/platform";
* import { DenoContext, DenoRuntime } from "@lishaduck/effect-platform-deno";
* import { assertEquals } from "@std/assert";
* import { Console, Effect } from "effect";
*
* const program = Effect.gen(function* () {
* // Access the Path service
* const path = yield* Path.Path;
*
* // Join parts of a path to create a complete file path
* const fileName = path.basename("some/directory/file.txt");
*
* assertEquals(fileName, "file.txt");
* });
*
* DenoRuntime.runMain(program.pipe(Effect.provide(DenoContext.layer)));
* ```
*
* @since 0.1.0
*/

Expand Down
30 changes: 24 additions & 6 deletions packages/platform-deno/src/DenoKeyValueStore.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
/**
* @since 1.0.0
* This module exposes database primitives.
* @module
*
* @since 0.1.2
*/

import * as KeyValueStore from "@effect/platform/KeyValueStore";
import type * as Layer from "effect/Layer";
import * as Layer from "effect/Layer";
import { makeKvStore } from "./internal/kv.ts";

/**
* Creates a {@linkcode KeyValueStore} layer that uses the Web-native {@linkcode localStorage} API.
*
* Values are stored between sessions.
*
* @since 1.0.0
* @category models
* @since 0.1.2
* @category layer
*/
export const layerLocalStorage: Layer.Layer<KeyValueStore.KeyValueStore> =
KeyValueStore.layerStorage(() => localStorage);
Expand All @@ -20,8 +25,21 @@ export const layerLocalStorage: Layer.Layer<KeyValueStore.KeyValueStore> =
*
* Values are stored only for the current session.
*
* @since 1.0.0
* @category models
* @since 0.1.2
* @category layer
*/
export const layerSessionStorage: Layer.Layer<KeyValueStore.KeyValueStore> =
KeyValueStore.layerStorage(() => sessionStorage);

/**
* Creates a {@linkcode KeyValueStore} layer that uses Deno’s cloud-native {@linkcode Deno.Kv} API.
*
* @remarks
* This does not support gradual adoption,
* and will fail semi-gracefully on non-`string` or {@linkcode Uint8Array} values.
*kvStore
* @since 0.1.2
* @category layer
*/
export const layerKv: Layer.Layer<KeyValueStore.KeyValueStore, never, never> =
Layer.effect(KeyValueStore.KeyValueStore, makeKvStore());
21 changes: 21 additions & 0 deletions packages/platform-deno/src/DenoPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,27 @@
* This module exposes path operations from the Deno Standard Library.
* @module
*
*
* @example
* ```ts
* import { Path } from "@effect/platform";
* import { DenoContext, DenoRuntime } from "@lishaduck/effect-platform-deno";
* import { assertEquals } from "@std/assert";
* import { Console, Effect } from "effect";
*
* const program = Effect.gen(function* () {
* // Access the Path service
* const path = yield* Path.Path;
*
* // Join parts of a path to create a complete file path
* const extension = path.extname("file.txt");
*
* assertEquals(extension, ".txt");
* });
*
* DenoRuntime.runMain(program.pipe(Effect.provide(DenoContext.layer)));
* ```
*
* @since 0.1.0
*/

Expand Down
8 changes: 8 additions & 0 deletions packages/platform-deno/src/DenoRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@
* This module exposes an {@link https://effect.website/docs/runtime/ | Effect Runtime} powered by Deno.
* @module
*
* @example
* ```ts
* import { DenoContext, DenoRuntime } from "@lishaduck/effect-platform-deno";
* import { Console, Effect } from "effect";
*
* DenoRuntime.runMain(Console.log("Hello, World").pipe(Effect.provide(DenoContext.layer)));
* ```
*
* @since 0.1.0
*/

Expand Down
9 changes: 5 additions & 4 deletions packages/platform-deno/src/DenoWorkerRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
* This modules exposes primitives for multithread-communication using the standard {@linkcode MessagePort} API.
* @module
*
* @since 1.0.0
* @since 0.1.1
*/

import { WorkerRunner as Runner, WorkerError } from "@effect/platform";
import {
Context,
Expand Down Expand Up @@ -34,7 +35,7 @@ if (typeof self !== "undefined" && "onconnect" in self) {
/**
* Constructs a {@linkplain Runner.PlatformRunner | runner} from a {@linkcode MessagePort}.
*
* @since 1.0.0
* @since 0.1.1
* @category constructors
*/
export const make: (self: MessagePort | Window) => Runner.PlatformRunner = (
Expand Down Expand Up @@ -199,7 +200,7 @@ export const make: (self: MessagePort | Window) => Runner.PlatformRunner = (
/**
* A {@linkplain Layer.Layer | layer} that provides a {@linkcode Runner.PlatformRunner | PlatformRunner} from {@linkcode self} to your app.
*
* @since 1.0.0
* @since 0.1.1
* @category layers
*/
export const layer: Layer.Layer<Runner.PlatformRunner> = Layer.sync(
Expand All @@ -210,7 +211,7 @@ export const layer: Layer.Layer<Runner.PlatformRunner> = Layer.sync(
/**
* A {@linkplain Layer.Layer | layer} that provides a {@linkcode Runner.PlatformRunner | PlatformRunner} from a {@linkcode MessagePort} to your app.
*
* @since 1.0.0
* @since 0.1.1
* @category layers
*/
export const layerMessagePort: (
Expand Down
102 changes: 102 additions & 0 deletions packages/platform-deno/src/internal/kv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { KeyValueStore } from "@effect/platform";
import type { PlatformError } from "@effect/platform/Error";
import { Effect, Option } from "effect";

export const makeKvStore = (): Effect.Effect<KeyValueStore.KeyValueStore> =>
Effect.promise(async () => {
const store = await Deno.openKv();
const encoder = new TextEncoder();

const get = (key: string): Effect.Effect<Option.Option<string>> =>
Effect.promise(async () => {
const val = await store.get([key]);
const value = val.value;

if (value == null) return Option.none();
if (typeof value === "string") return Option.some(value);

return Option.none();
});

const getUint8Array = (
key: string,
): Effect.Effect<Option.Option<Uint8Array>> =>
Effect.gen(function* () {
const val = yield* Effect.promise(async () => await store.get([key]));

const value = Option.fromNullable(val.key[0]);

return value.pipe(
Option.flatMap((value) => {
if (typeof value === "string") {
return Option.some(encoder.encode(value));
}
if (value instanceof Uint8Array) {
return Option.some(value);
}

return Option.none();
}),
);
});

const modifyUint8Array = (
key: string,
f: (value: Uint8Array) => Uint8Array,
): Effect.Effect<Option.Option<Uint8Array>, PlatformError> =>
Effect.flatMap(getUint8Array(key), (o) => {
if (Option.isNone(o)) {
return Effect.succeedNone;
}
const newValue = f(o.value);
return Effect.as(set(key, newValue), Option.some(newValue));
});

const set = (
key: string,
value: string | Uint8Array,
): Effect.Effect<void> =>
Effect.promise(async () => {
await store.set([key], value);
});

const remove = (key: string): Effect.Effect<void> =>
Effect.promise(async () => {
await store.delete([key]);
});

const getAll = <T>(): Deno.KvListIterator<T> =>
store.list<T>({ prefix: [] });

const clear = Effect.promise(async () => {
const entries = getAll();

const promises: Promise<void>[] = [];

for await (const entry of entries) {
promises.push(store.delete(entry.key));
}

await Promise.all(promises);
});

const size = Effect.promise(async () => {
const entries = getAll();

let size = 0;
for await (const _ of entries) {
size++;
}
return size;
});

return KeyValueStore.make({
get,
getUint8Array,
modifyUint8Array,
set,
remove,
clear,
size,
});
});
71 changes: 3 additions & 68 deletions packages/platform-deno/src/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,75 +23,10 @@
* ```
*/

/**
* Provides a platform context using Deno.
*
* @example
* ```ts
* import { Path } from "@effect/platform";
* import { DenoContext, DenoRuntime } from "@lishaduck/effect-platform-deno";
* import { assertEquals } from "@std/assert";
* import { Console, Effect } from "effect";
*
* const program = Effect.gen(function* () {
* // Access the Path service
* const path = yield* Path.Path;
*
* // Join parts of a path to create a complete file path
* const fileName = path.basename("some/directory/file.txt");
*
* assertEquals(fileName, "file.txt");
* });
*
* DenoRuntime.runMain(program.pipe(Effect.provide(DenoContext.layer)));
* ```
*
* @since 0.1.0
*/
export * as DenoContext from "./DenoContext.ts";

/**
* An effect runtime using Deno.
*
* @example
* ```ts
* import { DenoContext, DenoRuntime } from "@lishaduck/effect-platform-deno";
* import { Console, Effect } from "effect";
*
* DenoRuntime.runMain(Console.log("Hello, World").pipe(Effect.provide(DenoContext.layer)));
* ```
*
* @since 0.1.0
*/
export * as DenoFileSystem from "./DenoFileSystem.ts";
export * as DenoKeyValueStore from "./DenoKeyValueStore.ts";
export * as DenoRuntime from "./DenoRuntime.ts";

/**
* @since 0.1.0
*
* @example
* ```ts
* import { Path } from "@effect/platform";
* import { DenoContext, DenoRuntime } from "@lishaduck/effect-platform-deno";
* import { assertEquals } from "@std/assert";
* import { Console, Effect } from "effect";
*
* const program = Effect.gen(function* () {
* // Access the Path service
* const path = yield* Path.Path;
*
* // Join parts of a path to create a complete file path
* const extension = path.extname("file.txt");
*
* assertEquals(extension, ".txt");
* });
*
* DenoRuntime.runMain(program.pipe(Effect.provide(DenoContext.layer)));
* ```
*
*/
export * as DenoPath from "./DenoPath.ts";

/**
* @since 0.1.0
*/
export * as DenoWorker from "./DenoWorker.ts";
export * as DenoWorkerRunner from "./DenoWorkerRunner.ts";
Loading

0 comments on commit bbc4544

Please sign in to comment.