Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/mighty-yaks-wave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@openauthjs/openauth": minor
---

Added UnStorage Adapter
Binary file modified bun.lockb
Binary file not shown.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,8 @@
"prettier": "3.4.2",
"typescript": "5.6.3"
},
"private": true
"private": true,
"patchedDependencies": {
"[email protected]": "patches/[email protected]"
}
}
3 changes: 2 additions & 1 deletion packages/openauth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
"dependencies": {
"@standard-schema/spec": "1.0.0-beta.3",
"aws4fetch": "1.0.20",
"jose": "5.9.6"
"jose": "5.9.6",
"unstorage": "1.16.0"
},
"files": [
"src",
Expand Down
101 changes: 101 additions & 0 deletions packages/openauth/src/storage/unstorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* Configure OpenAuth to use unstorage as a store.
*
* This enables you to use any unstorage driver as a store.
* Please refer to [unstorage docs](https://unstorage.unjs.io/drivers) for details on possible drivers.
*
* By default, it uses the memory driver.
*
* :::caution
* The default memory driver is not meant to be used in production.
* :::
*
* ```ts
* import { UnStorage } from "@openauthjs/openauth/storage/unstorage"
*
* const storage = UnStorage()
*
* export default issuer({
* storage,
* // ...
* })
* ```
*
* Optionally, you can specify a driver.
*
* ```ts
* import fsDriver from "unstorage/drivers/fs";
*
* UnStorage({
* driver: fsDriver({ base: "./tmp" }),
* })
* ```
*
* @packageDocumentation
*/
import { joinKey, splitKey, StorageAdapter } from "./storage.js"
import { createStorage, type Driver as UnstorageDriver } from "unstorage"

type Entry = { value: Record<string, any> | undefined; expiry?: number }

export function UnStorage({
driver,
}: { driver?: UnstorageDriver } = {}): StorageAdapter {
const store = createStorage<Entry>({
driver: driver,
})

return {
async get(key: string[]) {
const k = joinKey(key)
const entry = await store.getItem(k)

if (!entry) {
return undefined
}

if (entry.expiry && Date.now() >= entry.expiry) {
await store.removeItem(k)
return undefined
}

return entry.value
},

async set(key: string[], value: any, expiry?: Date) {
const k = joinKey(key)

await store.setItem(k, {
value,
expiry: expiry ? expiry.getTime() : undefined,
} satisfies Entry)
},

async remove(key: string[]) {
const k = joinKey(key)
await store.removeItem(k)
},

async *scan(prefix: string[]) {
const now = Date.now()
const prefixStr = joinKey(prefix)

// Get all keys matching our prefix
const keys = await store.getKeys(prefixStr)

for (const key of keys) {
// Get the entry for this key
const entry = await store.getItem(key)

if (!entry) continue
if (entry.expiry && now >= entry.expiry) {
// Clean up expired entries as we go
await store.removeItem(key)
continue
}

yield [splitKey(key), entry.value]
}
},
}
}
94 changes: 94 additions & 0 deletions packages/openauth/test/unstorage.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { afterEach, setSystemTime } from "bun:test"
import { beforeEach, describe, expect, test } from "bun:test"
import { UnStorage } from "../src/storage/unstorage.js"

let storage = UnStorage()

beforeEach(async () => {
storage = UnStorage()
setSystemTime(new Date("1/1/2024"))
})

afterEach(() => {
setSystemTime()
})

describe("set", () => {
test("basic", async () => {
await storage.set(["users", "123"], { name: "Test User" })
const result = await storage.get(["users", "123"])
expect(result).toEqual({ name: "Test User" })
})

test("ttl", async () => {
await storage.set(
["temp", "key"],
{ value: "value" },
new Date(Date.now() + 100),
) // 100ms TTL
let result = await storage.get(["temp", "key"])
expect(result?.value).toBe("value")

setSystemTime(Date.now() + 150)
result = await storage.get(["temp", "key"])
expect(result).toBeUndefined()
})

test("nested", async () => {
const complexObj = {
id: 1,
nested: { a: 1, b: { c: 2 } },
array: [1, 2, 3],
}
await storage.set(["complex"], complexObj)
const result = await storage.get(["complex"])
expect(result).toEqual(complexObj)
})
})

describe("get", () => {
test("missing", async () => {
const result = await storage.get(["nonexistent"])
expect(result).toBeUndefined()
})

test("key", async () => {
await storage.set(["a", "b", "c"], { value: "nested" })
const result = await storage.get(["a", "b", "c"])
expect(result?.value).toBe("nested")
})
})

describe("remove", () => {
test("existing", async () => {
await storage.set(["test"], "value")
await storage.remove(["test"])
const result = await storage.get(["test"])
expect(result).toBeUndefined()
})

test("missing", async () => {
expect(storage.remove(["nonexistent"])).resolves.toBeUndefined()
})
})

describe("scan", () => {
test("all", async () => {
await storage.set(["users", "1"], { id: 1 })
await storage.set(["users", "2"], { id: 2 })
await storage.set(["other"], { id: 3 })
const results = await Array.fromAsync(storage.scan(["users"]))
expect(results).toHaveLength(2)
expect(results).toContainEqual([["users", "1"], { id: 1 }])
expect(results).toContainEqual([["users", "2"], { id: 2 }])
})

test("ttl", async () => {
await storage.set(["temp", "1"], "a", new Date(Date.now() + 100))
await storage.set(["temp", "2"], "b", new Date(Date.now() + 100))
await storage.set(["temp", "3"], "c")
expect(await Array.fromAsync(storage.scan(["temp"]))).toHaveLength(3)
setSystemTime(Date.now() + 150)
expect(await Array.fromAsync(storage.scan(["temp"]))).toHaveLength(1)
})
})
13 changes: 13 additions & 0 deletions patches/[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
diff --git a/dist/shared/unstorage.CoCt7NXC.mjs b/dist/shared/unstorage.CoCt7NXC.mjs
index bf498f41fce3403ca090ba91bb8d9772e38712f4..100cac0816b227db4e4b314246e2751f08eeeb0e 100644
--- a/dist/shared/unstorage.CoCt7NXC.mjs
+++ b/dist/shared/unstorage.CoCt7NXC.mjs
@@ -127,7 +127,7 @@ function joinKeys(...keys) {
}
function normalizeBaseKey(base) {
base = normalizeKey(base);
- return base ? base + ":" : "";
+ return base;
}
function filterKeyByDepth(key, depth) {
if (depth === void 0) {
21 changes: 14 additions & 7 deletions www/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const url = "https://openauth.js.org"
// https://astro.build/config
export default defineConfig({
site: url,
trailingSlash: 'always',
trailingSlash: "always",
devToolbar: {
enabled: false,
},
Expand Down Expand Up @@ -76,10 +76,7 @@ export default defineConfig({
components: {
Hero: "./src/components/Hero.astro",
},
customCss: [
"./src/custom.css",
"./src/styles/lander.css",
],
customCss: ["./src/custom.css", "./src/styles/lander.css"],
sidebar: [
{ label: "Intro", slug: "docs" },
{
Expand Down Expand Up @@ -118,11 +115,21 @@ export default defineConfig({
},
{
label: "UI",
items: ["docs/ui/theme", "docs/ui/select", "docs/ui/code", "docs/ui/password"],
items: [
"docs/ui/theme",
"docs/ui/select",
"docs/ui/code",
"docs/ui/password",
],
},
{
label: "Storage",
items: ["docs/storage/memory", "docs/storage/dynamo", "docs/storage/cloudflare"],
items: [
"docs/storage/memory",
"docs/storage/dynamo",
"docs/storage/cloudflare",
"docs/storage/unstorage",
],
},
],
}),
Expand Down
62 changes: 62 additions & 0 deletions www/src/content/docs/docs/storage/unstorage.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
title: UnStorage
editUrl: https://github.com/toolbeam/openauth/blob/master/packages/openauth/src/storage/unstorage.ts
description: Reference doc for the UnStorage storage adapter.
---

import { Segment, Section, NestedTitle, InlineSection } from 'toolbeam-docs-theme/components'
import { Tabs, TabItem } from '@astrojs/starlight/components'

<div class="tsdoc">
<Section type="about">
Configure OpenAuth to use [UnStorage](https://unstorage.unjs.io/) as a storage adapter


This enables you to use any UnStorage driver as a store.
Please refer to [UnStorage docs](https://unstorage.unjs.io/drivers) for details on possible drivers.

By default, it uses the memory driver.

:::caution
The default memory driver is not meant to be used in production.
:::

```ts
import { UnStorage } from "@openauthjs/openauth/storage/unstorage"

const storage = UnStorage()

export default issuer({
storage,
// ...
})
```

Optionally, you can specify a driver.

```ts
import fsDriver from "unstorage/drivers/fs";

UnStorage({
driver: fsDriver({ base: "./tmp" }),
})
```
</Section>
---
## Methods
### MemoryStorage
<Segment>
<Section type="signature">
```ts
UnStorage( { driver?: UnstorageDriver } )
```
</Section>
<Section type="parameters">
#### Parameters
- <p><code class="key">driver?</code> [<code class="type">UnStorage Driver</code>](https://unstorage.unjs.io/drivers)</p>
</Section>
<InlineSection>
**Returns** <code class="type">StorageAdapter</code>
</InlineSection>
</Segment>
</div>