Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
|
fd5d0c736f | 11 months ago |
22 changed files with 251 additions and 135 deletions
@ -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,3 @@ |
|||
about |
|||
import |
|||
use |
@ -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