import { describe, expect, it } from "@effect/vitest";import { Effect } from "effect"; import { StorageError } from "./fuma-runtime"; import { makeInMemoryBlobStore, pluginBlobStore, type OwnerPartitions } from "./blob"; // v2: owner partitions instead of a scope stack. Reads fall through// [user, org] (user = innermost); writes/deletes name an explicit owner.const partitions = (org: string, user: string | null): OwnerPartitions => ({ org, user,}); describe("pluginBlobStore", () => { it.effect("get returns user (innermost) value when both owners have one", () => Effect.gen(function* () { const store = makeInMemoryBlobStore(); yield* store.put("u/my-plugin", "k", "user-value"); yield* store.put("o/my-plugin", "k", "org-value"); const plugin = pluginBlobStore(store, partitions("o", "u"), "my-plugin"); const value = yield* plugin.get("k"); expect(value).toBe("user-value"); }), ); it.effect("get falls through to org when user partition is empty", () => Effect.gen(function* () { const store = makeInMemoryBlobStore(); yield* store.put("o/my-plugin", "k", "org-value"); const plugin = pluginBlobStore(store, partitions("o", "u"), "my-plugin"); const value = yield* plugin.get("k"); expect(value).toBe("org-value"); }), ); it.effect("get returns null when no owner has the key", () => Effect.gen(function* () { const store = makeInMemoryBlobStore(); const plugin = pluginBlobStore(store, partitions("o", "u"), "my-plugin"); const value = yield* plugin.get("k"); expect(value).toBeNull(); }), ); it.effect("has returns true when any owner has the key", () => Effect.gen(function* () { const store = makeInMemoryBlobStore(); yield* store.put("o/my-plugin", "k", "v"); const plugin = pluginBlobStore(store, partitions("o", "u"), "my-plugin"); const found = yield* plugin.has("k"); expect(found).toBe(true); }), ); it.effect("has returns false when no owner has the key", () => Effect.gen(function* () { const store = makeInMemoryBlobStore(); const plugin = pluginBlobStore(store, partitions("o", "u"), "my-plugin"); const found = yield* plugin.has("k"); expect(found).toBe(false); }), ); it.effect("namespaces are keyed by partition/pluginId — different plugins don't collide", () => Effect.gen(function* () { const store = makeInMemoryBlobStore(); yield* store.put("u/plugin-a", "k", "a-value"); yield* store.put("u/plugin-b", "k", "b-value"); const pluginA = pluginBlobStore(store, partitions("o", "u"), "plugin-a"); const pluginB = pluginBlobStore(store, partitions("o", "u"), "plugin-b"); expect(yield* pluginA.get("k")).toBe("a-value"); expect(yield* pluginB.get("k")).toBe("b-value"); }), ); it.effect("put rejects owner:user when the executor has no subject", () => Effect.gen(function* () { const store = makeInMemoryBlobStore(); // No user partition → owner:"user" writes fail. const plugin = pluginBlobStore(store, partitions("o", null), "my-plugin"); const err = yield* plugin.put("k", "v", { owner: "user" }).pipe(Effect.flip); expect(err).toBeInstanceOf(StorageError); // Write must not have reached the store. expect(yield* store.get("o/my-plugin", "k")).toBeNull(); }), ); it.effect("delete rejects owner:user when the executor has no subject", () => Effect.gen(function* () { const store = makeInMemoryBlobStore(); const plugin = pluginBlobStore(store, partitions("o", null), "my-plugin"); const err = yield* plugin.delete("k", { owner: "user" }).pipe(Effect.flip); expect(err).toBeInstanceOf(StorageError); }), );}); describe("BlobStore.getMany", () => { it.effect("returns hits keyed by namespace", () => Effect.gen(function* () { const store = makeInMemoryBlobStore(); yield* store.put("ns-a", "k", "a"); yield* store.put("ns-c", "k", "c"); const hits = yield* store.getMany(["ns-a", "ns-b", "ns-c"], "k"); expect(hits.size).toBe(2); expect(hits.get("ns-a")).toBe("a"); expect(hits.get("ns-b")).toBeUndefined(); expect(hits.get("ns-c")).toBe("c"); }), ); it.effect("empty namespaces returns empty map", () => Effect.gen(function* () { const store = makeInMemoryBlobStore(); const hits = yield* store.getMany([], "k"); expect(hits.size).toBe(0); }), );});
import { describe, expect, it } from "@effect/vitest";
import { Effect } from "effect";
import { StorageError } from "./fuma-runtime";
import { makeInMemoryBlobStore, pluginBlobStore, type OwnerPartitions } from "./blob";
// v2: owner partitions instead of a scope stack. Reads fall through
// [user, org] (user = innermost); writes/deletes name an explicit owner.
const partitions = (org: string, user: string | null): OwnerPartitions => ({
org,
user,
});
describe("pluginBlobStore", () => {
it.effect("get returns user (innermost) value when both owners have one", () =>
Effect.gen(function* () {
const store = makeInMemoryBlobStore();
yield* store.put("u/my-plugin", "k", "user-value");
yield* store.put("o/my-plugin", "k", "org-value");
const plugin = pluginBlobStore(store, partitions("o", "u"), "my-plugin");
const value = yield* plugin.get("k");
expect(value).toBe("user-value");
}),
);
it.effect("get falls through to org when user partition is empty", () =>
Effect.gen(function* () {
const store = makeInMemoryBlobStore();
yield* store.put("o/my-plugin", "k", "org-value");
const plugin = pluginBlobStore(store, partitions("o", "u"), "my-plugin");
const value = yield* plugin.get("k");
expect(value).toBe("org-value");
}),
);
it.effect("get returns null when no owner has the key", () =>
Effect.gen(function* () {
const store = makeInMemoryBlobStore();
const plugin = pluginBlobStore(store, partitions("o", "u"), "my-plugin");
const value = yield* plugin.get("k");
expect(value).toBeNull();
}),
);
it.effect("has returns true when any owner has the key", () =>
Effect.gen(function* () {
const store = makeInMemoryBlobStore();
yield* store.put("o/my-plugin", "k", "v");
const plugin = pluginBlobStore(store, partitions("o", "u"), "my-plugin");
const found = yield* plugin.has("k");
expect(found).toBe(true);
}),
);
it.effect("has returns false when no owner has the key", () =>
Effect.gen(function* () {
const store = makeInMemoryBlobStore();
const plugin = pluginBlobStore(store, partitions("o", "u"), "my-plugin");
const found = yield* plugin.has("k");
expect(found).toBe(false);
}),
);
it.effect("namespaces are keyed by partition/pluginId — different plugins don't collide", () =>
Effect.gen(function* () {
const store = makeInMemoryBlobStore();
yield* store.put("u/plugin-a", "k", "a-value");
yield* store.put("u/plugin-b", "k", "b-value");
const pluginA = pluginBlobStore(store, partitions("o", "u"), "plugin-a");
const pluginB = pluginBlobStore(store, partitions("o", "u"), "plugin-b");
expect(yield* pluginA.get("k")).toBe("a-value");
expect(yield* pluginB.get("k")).toBe("b-value");
}),
);
it.effect("put rejects owner:user when the executor has no subject", () =>
Effect.gen(function* () {
const store = makeInMemoryBlobStore();
// No user partition → owner:"user" writes fail.
const plugin = pluginBlobStore(store, partitions("o", null), "my-plugin");
const err = yield* plugin.put("k", "v", { owner: "user" }).pipe(Effect.flip);
expect(err).toBeInstanceOf(StorageError);
// Write must not have reached the store.
expect(yield* store.get("o/my-plugin", "k")).toBeNull();
}),
);
it.effect("delete rejects owner:user when the executor has no subject", () =>
Effect.gen(function* () {
const store = makeInMemoryBlobStore();
const plugin = pluginBlobStore(store, partitions("o", null), "my-plugin");
const err = yield* plugin.delete("k", { owner: "user" }).pipe(Effect.flip);
expect(err).toBeInstanceOf(StorageError);
}),
);
});
describe("BlobStore.getMany", () => {
it.effect("returns hits keyed by namespace", () =>
Effect.gen(function* () {
const store = makeInMemoryBlobStore();
yield* store.put("ns-a", "k", "a");
yield* store.put("ns-c", "k", "c");
const hits = yield* store.getMany(["ns-a", "ns-b", "ns-c"], "k");
expect(hits.size).toBe(2);
expect(hits.get("ns-a")).toBe("a");
expect(hits.get("ns-b")).toBeUndefined();
expect(hits.get("ns-c")).toBe("c");
}),
);
it.effect("empty namespaces returns empty map", () =>
Effect.gen(function* () {
const store = makeInMemoryBlobStore();
const hits = yield* store.getMany([], "k");
expect(hits.size).toBe(0);
}),
);
});