Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
"lru-cache": "^10.4.3",
"node-fetch-native": "^1.6.6",
"ofetch": "^1.4.1",
"ufo": "^1.6.1"
"ufo": "^1.6.1",
"undio": "^0.2.0"
},
"devDependencies": {
"@azure/app-configuration": "^1.9.0",
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

71 changes: 66 additions & 5 deletions src/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
filterKeyByDepth,
filterKeyByBase,
} from "./utils";
import { toBlob, toReadableStream, toUint8Array } from "undio";

interface StorageCTX {
mounts: Record<string, Driver>;
Expand Down Expand Up @@ -172,12 +173,71 @@
const { relativeKey, driver } = getMount(key);
return asyncCall(driver.hasItem, relativeKey, opts);
},
getItem(key: string, opts = {}) {
getItem: async (
key: string,
opts: TransactionOptions & {
type?: "json" | "text" | "bytes" | "stream" | "blob";
} = {}
) => {
key = normalizeKey(key);
const { relativeKey, driver } = getMount(key);
return asyncCall(driver.getItem, relativeKey, opts).then(
(value) => destr(value) as StorageValue
);

const type = opts.type;

if (type === "bytes" || type === "stream" || type === "blob") {
const raw = driver.getItemRaw
? await asyncCall(driver.getItemRaw, relativeKey, opts)
: await asyncCall(driver.getItem, relativeKey, opts).then((val) =>
deserializeRaw(val)

Check warning on line 191 in src/storage.ts

View check run for this annotation

Codecov / codecov/patch

src/storage.ts#L190-L191

Added lines #L190 - L191 were not covered by tests
);

if (raw === null || raw === undefined) {
return null;
}

Check warning on line 196 in src/storage.ts

View check run for this annotation

Codecov / codecov/patch

src/storage.ts#L195-L196

Added lines #L195 - L196 were not covered by tests

switch (type) {
case "bytes": {
return await toUint8Array(raw);
}
case "blob": {
if (typeof Blob === "undefined") {
throw new TypeError("Blob is not supported in this environment.");
}

Check warning on line 205 in src/storage.ts

View check run for this annotation

Codecov / codecov/patch

src/storage.ts#L204-L205

Added lines #L204 - L205 were not covered by tests
return await toBlob(raw);
}
case "stream": {
if (typeof ReadableStream === "undefined") {
throw new TypeError(
"ReadableStream is not supported in this environment."
);
}

Check warning on line 213 in src/storage.ts

View check run for this annotation

Codecov / codecov/patch

src/storage.ts#L210-L213

Added lines #L210 - L213 were not covered by tests
return await toReadableStream(raw);
}
default: {
throw new Error(`Invalid binary type: ${type}`);
}

Check warning on line 218 in src/storage.ts

View check run for this annotation

Codecov / codecov/patch

src/storage.ts#L217-L218

Added lines #L217 - L218 were not covered by tests
}
}

const value = await asyncCall(driver.getItem, relativeKey, opts);
if (value == null) {
return null;
}

switch (type) {
case "text": {
return typeof value === "string" ? value : String(value);
}
case "json": {
if (typeof value === "string") {
return JSON.parse(value);
}
return value;
}

Check warning on line 236 in src/storage.ts

View check run for this annotation

Codecov / codecov/patch

src/storage.ts#L235-L236

Added lines #L235 - L236 were not covered by tests
default: {
return destr(value);
}
}
},
getItems(
items: (string | { key: string; options?: TransactionOptions })[],
Expand Down Expand Up @@ -473,7 +533,8 @@
},
// Aliases
keys: (base, opts = {}) => storage.getKeys(base, opts),
get: (key: string, opts = {}) => storage.getItem(key, opts),
get: ((key: string, opts: TransactionOptions = {}) =>
storage.getItem(key, opts)) as Storage<T>["get"],
set: (key: string, value: T, opts = {}) =>
storage.setItem(key, value, opts),
has: (key: string, opts = {}) => storage.hasItem(key, opts),
Expand Down
38 changes: 35 additions & 3 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ type MaybePromise<T> = T | Promise<T>;

type MaybeDefined<T> = T extends any ? T : any;

// prettier-ignore
export type JSONValue = string | number | boolean | null | JSONObject | JSONArray;
// prettier-ignore
export interface JSONObject { [key: string]: JSONValue; }
export type JSONArray = JSONValue[];

export type Unwatch = () => MaybePromise<void>;

export interface StorageMeta {
Expand Down Expand Up @@ -96,14 +102,40 @@ export interface Storage<T extends StorageValue = StorageValue> {

getItem<
U extends Extract<T, StorageDefinition>,
K extends string & keyof StorageItemMap<U>,
K extends keyof StorageItemMap<U>,
>(
key: K,
ops?: TransactionOptions
opts?: TransactionOptions & { type?: undefined }
): Promise<StorageItemType<T, K> | null>;

getItem(
key: string,
opts: { type: "text" } & TransactionOptions
): Promise<string | null>;

getItem(
key: string,
opts: { type: "json" } & TransactionOptions
): Promise<JSONValue | null>;

getItem(
key: string,
opts: { type: "bytes" } & TransactionOptions
): Promise<Uint8Array | null>;

getItem(
key: string,
opts: { type: "stream" } & TransactionOptions
): Promise<ReadableStream | null>;

getItem(
key: string,
opts: { type: "blob" } & TransactionOptions
): Promise<Blob | null>;

getItem<R = StorageItemType<T, string>>(
key: string,
opts?: TransactionOptions
opts?: TransactionOptions & { type?: undefined }
): Promise<R | null>;

/** @experimental */
Expand Down
54 changes: 54 additions & 0 deletions test/storage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 +289,57 @@ describe("Regression", () => {
]);
});
});

describe("get() with type option", () => {
const storage = createStorage();

it("should get JSON object with type=json", async () => {
const obj = { foo: "bar", num: 42 };
await storage.setItem("json-key", obj);
const result = await storage.get("json-key", { type: "json" });
expect(result).toEqual(obj);
});

it("should get string with type=text", async () => {
await storage.setItem("text-key", "hello world");
const result = await storage.get("text-key", { type: "text" });
expect(result).toBe("hello world");
});

it("should get bytes with type=bytes", async () => {
const bytes = new Uint8Array([1, 2, 3, 4]);
await storage.setItemRaw("bytes-key", bytes);
const result = await storage.get("bytes-key", { type: "bytes" });
const len = result?.length || result?.byteLength;
expect(len).toBe(4);
});

it("should get bytes with type=stream", async () => {
const bytes = new Uint8Array([1, 2, 3, 4]);
await storage.setItemRaw("stream-key", bytes);
const result = await storage.get("stream-key", { type: "stream" });
expect(result).toBeInstanceOf(ReadableStream);
const reader = result?.getReader();
if (!reader) {
throw new Error("Reader is not defined");
}
const { done, value } = await reader.read();
expect(done).toBe(false);
expect(value).toEqual(bytes);
const len = value?.length || value?.byteLength;
expect(len).toBe(4);

const { done: doneAfter, value: valueAfter } = await reader.read();
expect(doneAfter).toBe(true);
expect(valueAfter).toBeUndefined();
});

it("should get bytes with type=blob", async () => {
const bytes = new Uint8Array([1, 2, 3, 4]);
await storage.setItemRaw("blob-key", bytes);
const result = await storage.get("blob-key", { type: "blob" });
expect(result).toBeInstanceOf(Blob);
const arrayBuffer = await result?.arrayBuffer();
expect(new Uint8Array(arrayBuffer || [])).toEqual(bytes);
});
});