Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
|
fd5d0c736f | 2 years ago |
@ -1,3 +1,5 @@ |
||||
deno.d.ts |
||||
.vscode |
||||
.local |
||||
.output |
||||
web/*.js |
||||
|
@ -1,3 +1,51 @@ |
||||
# typescript/storage |
||||
|
||||
[](https://dev.azure.com/thunderk/typescript/_build?pipelineNameFilter=storage) |
||||
|
||||
## About |
||||
|
||||
Javascript/Typescript persistent storage, with key-value stores as foundation. |
||||
|
||||
## Import |
||||
|
||||
In deno: |
||||
|
||||
```typescript |
||||
import { getLocalStorage } from "https://js.thunderk.net/storage/mod.ts"; |
||||
``` |
||||
|
||||
In browser: |
||||
|
||||
```html |
||||
<script type="module"> |
||||
import { getLocalStorage } from "https://js.thunderk.net/storage/mod.js"; |
||||
</script> |
||||
``` |
||||
|
||||
## Use |
||||
|
||||
To get a storage locally persistent (saved in browser data or on disk for Deno): |
||||
|
||||
```javascript |
||||
const storage = getLocalStorage("myapp"); |
||||
await storage.get("key"); // => null |
||||
await storage.set("key", "value"); |
||||
await storage.get("key"); // => "value" |
||||
``` |
||||
|
||||
To get a storage remotely persistent (saved on a compliant server): |
||||
|
||||
```javascript |
||||
const storage = getRemoteStorage("myapp", "https://tk-storage.example.io/", { |
||||
shared: true, |
||||
}); |
||||
await storage.get("key"); // => null |
||||
await storage.set("key", "value"); |
||||
await storage.get("key"); // => "value" |
||||
``` |
||||
|
||||
Run a server for remote storage: |
||||
|
||||
```shell |
||||
./cli.ts |
||||
``` |
||||
|
@ -0,0 +1,11 @@ |
||||
#!./run |
||||
|
||||
import { getLocalStorage } from "./src/main.ts"; |
||||
import { startRestServer } from "./src/server.ts"; |
||||
|
||||
const PORT = 5001; |
||||
|
||||
if (import.meta.main) { |
||||
await startRestServer(PORT, getLocalStorage("tk-storage-server")); |
||||
console.log(`Server running, use http://localhost:${PORT} to connect`); |
||||
} |
@ -1,4 +1,4 @@ |
||||
export { readAll } from "https://deno.land/std@0.104.0/io/util.ts"; |
||||
export { readAll } from "https://deno.land/std@0.107.0/io/util.ts"; |
||||
|
||||
export { json, opine } from "https://deno.land/x/opine@1.7.2/mod.ts"; |
||||
export { opineCors } from "https://deno.land/x/cors@v1.2.2/mod.ts"; |
||||
export { Application } from "https://deno.land/x/oak@v9.0.1/mod.ts"; |
||||
export { oakCors } from "https://deno.land/x/cors@v1.2.2/mod.ts"; |
||||
|
@ -1 +1 @@ |
||||
export { v1 as uuid1 } from "https://deno.land/std@0.104.0/uuid/mod.ts"; |
||||
export { v1 as uuid1 } from "https://deno.land/std@0.107.0/uuid/mod.ts"; |
||||
|
@ -0,0 +1,3 @@ |
||||
## About |
||||
|
||||
Javascript/Typescript persistent storage, with key-value stores as foundation. |
@ -0,0 +1,15 @@ |
||||
## Import |
||||
|
||||
In deno: |
||||
|
||||
```typescript |
||||
import { getLocalStorage } from "https://js.thunderk.net/storage/mod.ts"; |
||||
``` |
||||
|
||||
In browser: |
||||
|
||||
```html |
||||
<script type="module"> |
||||
import { getLocalStorage } from "https://js.thunderk.net/storage/mod.js"; |
||||
</script> |
||||
``` |
@ -0,0 +1,27 @@ |
||||
## Use |
||||
|
||||
To get a storage locally persistent (saved in browser data or on disk for Deno): |
||||
|
||||
```javascript |
||||
const storage = getLocalStorage("myapp"); |
||||
await storage.get("key"); // => null |
||||
await storage.set("key", "value"); |
||||
await storage.get("key"); // => "value" |
||||
``` |
||||
|
||||
To get a storage remotely persistent (saved on a compliant server): |
||||
|
||||
```javascript |
||||
const storage = getRemoteStorage("myapp", "https://tk-storage.example.io/", { |
||||
shared: true, |
||||
}); |
||||
await storage.get("key"); // => null |
||||
await storage.set("key", "value"); |
||||
await storage.get("key"); // => "value" |
||||
``` |
||||
|
||||
Run a server for remote storage: |
||||
|
||||
```shell |
||||
./cli.ts |
||||
``` |
@ -1,70 +0,0 @@ |
||||
#!./run |
||||
import { KeyValueStorage } from "./basic.ts"; |
||||
import { json, opine, opineCors, readAll } from "./deps.server.ts"; |
||||
import { getLocalStorage } from "./mod.ts"; |
||||
import { HEADER_REPLYIER, HEADER_REQUESTER } from "./remote.ts"; |
||||
|
||||
const PORT = 5001; |
||||
|
||||
type Server = { |
||||
close: () => Promise<void>; |
||||
}; |
||||
|
||||
/** |
||||
* Start a server compliant with RestRemoteStorage |
||||
*/ |
||||
export function startRestServer( |
||||
port: number, |
||||
storage: KeyValueStorage, |
||||
): Promise<Server> { |
||||
const app = opine(); |
||||
app.use( |
||||
opineCors({ exposedHeaders: [HEADER_REPLYIER] }), |
||||
json(), |
||||
(req, res, next) => { |
||||
if (req.get(HEADER_REQUESTER)) { |
||||
res.set(HEADER_REPLYIER, "ok"); |
||||
next(); |
||||
} else { |
||||
res.setStatus(400).send(); |
||||
} |
||||
}, |
||||
); |
||||
app.get("*", (req, res, next) => { |
||||
storage.get(req.path).then((result) => { |
||||
if (result === null) { |
||||
res.setStatus(404).send(); |
||||
} else { |
||||
res.send(result); |
||||
} |
||||
}).catch(next); |
||||
}); |
||||
app.put("*", (req, res, next) => { |
||||
readAll(req.body).then((body) => { |
||||
const value = new TextDecoder().decode(body); |
||||
storage.set(req.path, value).then(() => { |
||||
res.send(); |
||||
}).catch(next); |
||||
}).catch(next); |
||||
}); |
||||
app.delete("*", (req, res, next) => { |
||||
storage.set(req.path, null).then(() => { |
||||
res.send(); |
||||
}).catch(next); |
||||
}); |
||||
|
||||
return new Promise((resolve) => { |
||||
const server = app.listen(port, () => { |
||||
resolve({ |
||||
close: async () => { |
||||
server.close(); |
||||
}, |
||||
}); |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
if (import.meta.main) { |
||||
await startRestServer(PORT, getLocalStorage("tk-storage-server")); |
||||
console.log(`Server running, use http://localhost:${PORT} to connect`); |
||||
} |
@ -1,4 +1,4 @@ |
||||
import { uuid1 } from "./deps.ts"; |
||||
import { uuid1 } from "../deps.ts"; |
||||
|
||||
/** |
||||
* Standard interface for the simplest key-value store |
@ -1,4 +1,4 @@ |
||||
import { getLocalStorage, getMemoryStorage, getRemoteStorage } from "./mod.ts"; |
||||
import { getLocalStorage, getMemoryStorage, getRemoteStorage } from "./main.ts"; |
||||
import { MemoryStorage, ScopedStorage } from "./basic.ts"; |
||||
import { describe, expect, it, runTestServer } from "./testing.ts"; |
||||
|
@ -0,0 +1,63 @@ |
||||
import { |
||||
KeyValueStorage, |
||||
MemoryStorage, |
||||
RefScopedStorage, |
||||
ScopedStorage, |
||||
} from "./basic.ts"; |
||||
import { LocalStorage } from "./local.ts"; |
||||
import { RestRemoteStorage } from "./remote.ts"; |
||||
|
||||
/** |
||||
* Base type for storage usage |
||||
*/ |
||||
export type TKStorage = KeyValueStorage; |
||||
|
||||
/** |
||||
* Exposed classes |
||||
*/ |
||||
export { |
||||
LocalStorage, |
||||
MemoryStorage, |
||||
RefScopedStorage, |
||||
RestRemoteStorage, |
||||
ScopedStorage, |
||||
}; |
||||
|
||||
/** |
||||
* Get the best "local" storage available |
||||
*/ |
||||
export function getLocalStorage(appname: string): TKStorage { |
||||
try { |
||||
return new ScopedStorage(new LocalStorage(), appname); |
||||
} catch { |
||||
console.warn( |
||||
"No persistent storage available, using in-memory volatile storage", |
||||
); |
||||
return new MemoryStorage(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Get a remote storage, based on an URI |
||||
* |
||||
* If *shared* is set to true, a namespace will be used to avoid collisions, using getLocalStorage to persist it |
||||
*/ |
||||
export function getRemoteStorage( |
||||
appname: string, |
||||
uri: string, |
||||
options = { shared: false }, |
||||
): TKStorage { |
||||
let storage: TKStorage = new RestRemoteStorage(appname, uri); |
||||
storage = new ScopedStorage(storage, appname); |
||||
if (options.shared) { |
||||
storage = new RefScopedStorage(getLocalStorage(appname), storage); |
||||
} |
||||
return storage; |
||||
} |
||||
|
||||
/** |
||||
* Get a in-memory volatile storage |
||||
*/ |
||||
export function getMemoryStorage(): TKStorage { |
||||
return new MemoryStorage(); |
||||
} |
@ -0,0 +1,56 @@ |
||||
import { KeyValueStorage } from "./basic.ts"; |
||||
import { Application, oakCors, readAll } from "../deps.server.ts"; |
||||
import { HEADER_REPLYIER, HEADER_REQUESTER } from "./remote.ts"; |
||||
|
||||
type Server = { |
||||
close: () => Promise<void>; |
||||
}; |
||||
|
||||
/** |
||||
* Start a server compliant with RestRemoteStorage |
||||
*/ |
||||
export function startRestServer( |
||||
port: number, |
||||
storage: KeyValueStorage, |
||||
): Server { |
||||
const app = new Application(); |
||||
const controller = new AbortController(); |
||||
|
||||
app.use(oakCors()); |
||||
app.use(async (context) => { |
||||
if (context.request.headers.get(HEADER_REQUESTER)) { |
||||
context.response.headers.set(HEADER_REPLYIER, "ok"); |
||||
|
||||
const method = context.request.method; |
||||
const path = context.request.url.pathname; |
||||
|
||||
if (method == "GET") { |
||||
const result = await storage.get(path); |
||||
if (result === null) { |
||||
context.response.status = 404; |
||||
} else { |
||||
context.response.body = result; |
||||
} |
||||
} else if (method == "PUT") { |
||||
const body = context.request.body({ type: "reader" }); |
||||
const data = await readAll(body.value); |
||||
const value = new TextDecoder().decode(data); |
||||
await storage.set(path, value); |
||||
} else if (method == "DELETE") { |
||||
await storage.set(path, null); |
||||
} else { |
||||
context.response.status = 405; |
||||
} |
||||
} else { |
||||
context.response.status = 400; |
||||
} |
||||
}); |
||||
|
||||
const listen = app.listen({ port, signal: controller.signal }); |
||||
return { |
||||
close: async () => { |
||||
controller.abort(); |
||||
await listen; |
||||
}, |
||||
}; |
||||
} |
Loading…
Reference in new issue