Skip to content

Commit

Permalink
add binary support to KeyValueStore (#3494)
Browse files Browse the repository at this point in the history
  • Loading branch information
tim-smart authored Aug 20, 2024
1 parent f2c8dbb commit 413994c
Show file tree
Hide file tree
Showing 3 changed files with 105 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/eight-poems-buy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/platform": patch
---

add binary support to KeyValueStore
25 changes: 24 additions & 1 deletion packages/platform/src/KeyValueStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,15 @@ export interface KeyValueStore {
*/
readonly get: (key: string) => Effect.Effect<Option.Option<string>, PlatformError.PlatformError>

/**
* Returns the value of the specified key if it exists.
*/
readonly getUint8Array: (key: string) => Effect.Effect<Option.Option<Uint8Array>, PlatformError.PlatformError>

/**
* Sets the value of the specified key.
*/
readonly set: (key: string, value: string) => Effect.Effect<void, PlatformError.PlatformError>
readonly set: (key: string, value: string | Uint8Array) => Effect.Effect<void, PlatformError.PlatformError>

/**
* Removes the specified key.
Expand All @@ -64,6 +69,14 @@ export interface KeyValueStore {
f: (value: string) => string
) => Effect.Effect<Option.Option<string>, PlatformError.PlatformError>

/**
* Updates the value of the specified key if it exists.
*/
readonly modifyUint8Array: (
key: string,
f: (value: Uint8Array) => Uint8Array
) => Effect.Effect<Option.Option<Uint8Array>, PlatformError.PlatformError>

/**
* Returns true if the KeyValueStore contains the specified key.
*/
Expand Down Expand Up @@ -104,6 +117,16 @@ export const make: (
impl: Omit<KeyValueStore, typeof TypeId | "has" | "modify" | "isEmpty" | "forSchema"> & Partial<KeyValueStore>
) => KeyValueStore = internal.make

/**
* @since 1.0.0
* @category constructors
*/
export const makeStringOnly: (
impl: Pick<KeyValueStore, "get" | "remove" | "clear" | "size"> & Partial<Omit<KeyValueStore, "set">> & {
readonly set: (key: string, value: string) => Effect.Effect<void, PlatformError.PlatformError>
}
) => KeyValueStore = internal.makeStringOnly

/**
* @since 1.0.0
* @category combinators
Expand Down
83 changes: 76 additions & 7 deletions packages/platform/src/internal/keyValueStore.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import * as Schema from "@effect/schema/Schema"
import * as Context from "effect/Context"
import * as Effect from "effect/Effect"
import * as Either from "effect/Either"
import * as Encoding from "effect/Encoding"
import type { LazyArg } from "effect/Function"
import { dual, pipe } from "effect/Function"
import { dual, identity, pipe } from "effect/Function"
import * as Layer from "effect/Layer"
import * as Option from "effect/Option"
import * as PlatformError from "../Error.js"
Expand All @@ -21,7 +23,10 @@ export const keyValueStoreTag = Context.GenericTag<KeyValueStore.KeyValueStore>(
/** @internal */
export const make: (
impl:
& Omit<KeyValueStore.KeyValueStore, KeyValueStore.TypeId | "has" | "modify" | "isEmpty" | "forSchema">
& Omit<
KeyValueStore.KeyValueStore,
KeyValueStore.TypeId | "has" | "modify" | "modifyUint8Array" | "isEmpty" | "forSchema"
>
& Partial<KeyValueStore.KeyValueStore>
) => KeyValueStore.KeyValueStore = (impl) =>
keyValueStoreTag.of({
Expand All @@ -42,12 +47,55 @@ export const make: (
)
}
),
modifyUint8Array: (key, f) =>
Effect.flatMap(
impl.getUint8Array(key),
(o) => {
if (Option.isNone(o)) {
return Effect.succeedNone
}
const newValue = f(o.value)
return Effect.as(
impl.set(key, newValue),
Option.some(newValue)
)
}
),
forSchema(schema) {
return makeSchemaStore(this, schema)
},
...impl
})

/** @internal */
export const makeStringOnly: (
impl:
& Pick<
KeyValueStore.KeyValueStore,
"get" | "remove" | "clear" | "size"
>
& Partial<Omit<KeyValueStore.KeyValueStore, "set">>
& { readonly set: (key: string, value: string) => Effect.Effect<void, PlatformError.PlatformError> }
) => KeyValueStore.KeyValueStore = (impl) => {
const encoder = new TextEncoder()
return make({
...impl,
getUint8Array: (key) =>
impl.get(key).pipe(
Effect.map(Option.map((value) =>
Either.match(Encoding.decodeBase64(value), {
onLeft: () => encoder.encode(value),
onRight: identity
})
))
),
set: (key, value) =>
typeof value === "string"
? impl.set(key, value)
: Effect.suspend(() => impl.set(key, Encoding.encodeBase64(value)))
})
}

/** @internal */
export const prefix = dual<
(prefix: string) => <S extends KeyValueStore.KeyValueStore.AnyStore>(self: S) => S,
Expand Down Expand Up @@ -119,11 +167,23 @@ const makeSchemaStore = <A, I, R>(

/** @internal */
export const layerMemory = Layer.sync(keyValueStoreTag, () => {
const store = new Map<string, string>()
const store = new Map<string, string | Uint8Array>()
const encoder = new TextEncoder()

return make({
get: (key: string) => Effect.sync(() => Option.fromNullable(store.get(key))),
set: (key: string, value: string) => Effect.sync(() => store.set(key, value)),
get: (key: string) =>
Effect.sync(() =>
Option.fromNullable(store.get(key)).pipe(
Option.map((value) => typeof value === "string" ? value : Encoding.encodeBase64(value))
)
),
getUint8Array: (key: string) =>
Effect.sync(() =>
Option.fromNullable(store.get(key)).pipe(
Option.map((value) => typeof value === "string" ? encoder.encode(value) : value)
)
),
set: (key: string, value: string | Uint8Array) => Effect.sync(() => store.set(key, value)),
remove: (key: string) => Effect.sync(() => store.delete(key)),
clear: Effect.sync(() => store.clear()),
size: Effect.sync(() => store.size)
Expand Down Expand Up @@ -152,7 +212,16 @@ export const layerFileSystem = (directory: string) =>
(sysError) => sysError.reason === "NotFound" ? Effect.succeed(Option.none()) : Effect.fail(sysError)
)
),
set: (key: string, value: string) => fs.writeFileString(keyPath(key), value),
getUint8Array: (key: string) =>
pipe(
Effect.map(fs.readFile(keyPath(key)), Option.some),
Effect.catchTag(
"SystemError",
(sysError) => sysError.reason === "NotFound" ? Effect.succeed(Option.none()) : Effect.fail(sysError)
)
),
set: (key: string, value: string | Uint8Array) =>
typeof value === "string" ? fs.writeFileString(keyPath(key), value) : fs.writeFile(keyPath(key), value),
remove: (key: string) => fs.remove(keyPath(key)),
has: (key: string) => fs.exists(keyPath(key)),
clear: Effect.zipRight(
Expand Down Expand Up @@ -189,7 +258,7 @@ const storageError = (props: Omit<Parameters<typeof PlatformError.SystemError>[0
export const layerStorage = (evaluate: LazyArg<Storage>) =>
Layer.sync(keyValueStoreTag, () => {
const storage = evaluate()
return make({
return makeStringOnly({
get: (key: string) =>
Effect.try({
try: () => Option.fromNullable(storage.getItem(key)),
Expand Down

0 comments on commit 413994c

Please sign in to comment.