Use oak instead of opine
This commit is contained in:
parent
75487c3278
commit
fd5d0c736f
22 changed files with 251 additions and 135 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,3 +1,5 @@
|
|||
deno.d.ts
|
||||
.vscode
|
||||
.local
|
||||
.output
|
||||
web/*.js
|
||||
|
|
48
README.md
48
README.md
|
@ -1,3 +1,51 @@
|
|||
# typescript/storage
|
||||
|
||||
[![Build Status](https://thunderk.visualstudio.com/typescript/_apis/build/status/storage?branchName=master)](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
|
||||
```
|
||||
|
|
11
cli.ts
Executable file
11
cli.ts
Executable file
|
@ -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";
|
||||
|
|
|
@ -2,6 +2,4 @@ export {
|
|||
describe,
|
||||
expect,
|
||||
it,
|
||||
} from "https://js.thunderk.net/devtools@1.3.0/testing.ts";
|
||||
|
||||
export { getAvailablePort } from "https://deno.land/x/port@1.0.0/mod.ts";
|
||||
} from "https://js.thunderk.net/testing@1.0.0/mod.ts";
|
||||
|
|
2
deps.ts
2
deps.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";
|
||||
|
|
3
doc/about.md
Normal file
3
doc/about.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## About
|
||||
|
||||
Javascript/Typescript persistent storage, with key-value stores as foundation.
|
15
doc/import.md
Normal file
15
doc/import.md
Normal file
|
@ -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>
|
||||
```
|
3
doc/index
Normal file
3
doc/index
Normal file
|
@ -0,0 +1,3 @@
|
|||
about
|
||||
import
|
||||
use
|
27
doc/use.md
Normal file
27
doc/use.md
Normal file
|
@ -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
|
||||
```
|
59
mod.ts
59
mod.ts
|
@ -3,61 +3,24 @@ import {
|
|||
MemoryStorage,
|
||||
RefScopedStorage,
|
||||
ScopedStorage,
|
||||
} from "./basic.ts";
|
||||
import { LocalStorage } from "./local.ts";
|
||||
import { RestRemoteStorage } from "./remote.ts";
|
||||
} from "./src/basic.ts";
|
||||
import {
|
||||
getLocalStorage,
|
||||
getMemoryStorage,
|
||||
getRemoteStorage,
|
||||
} from "./src/main.ts";
|
||||
import { LocalStorage } from "./src/local.ts";
|
||||
import { RestRemoteStorage } from "./src/remote.ts";
|
||||
|
||||
/**
|
||||
* Base type for storage usage
|
||||
*/
|
||||
export type TKStorage = KeyValueStorage;
|
||||
|
||||
/**
|
||||
* Exposed classes
|
||||
*/
|
||||
export {
|
||||
getLocalStorage,
|
||||
getMemoryStorage,
|
||||
getRemoteStorage,
|
||||
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();
|
||||
}
|
||||
|
|
70
server.ts
70
server.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";
|
||||
|
63
src/main.ts
Normal file
63
src/main.ts
Normal file
|
@ -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();
|
||||
}
|
56
src/server.ts
Normal file
56
src/server.ts
Normal file
|
@ -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;
|
||||
},
|
||||
};
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
import { KeyValueStorage, MemoryStorage } from "./basic.ts";
|
||||
import { startRestServer } from "./server.ts";
|
||||
import { describe, expect, getAvailablePort, it } from "./deps.testing.ts";
|
||||
import { describe, expect, it } from "../deps.testing.ts";
|
||||
|
||||
export { describe, expect, it };
|
||||
|
||||
const PORT = 5002;
|
||||
|
||||
/**
|
||||
* Basic high-level test suite for any kind storage
|
||||
*/
|
||||
|
@ -39,14 +41,9 @@ export async function disableLocalStorage(
|
|||
export async function runTestServer(
|
||||
body: (url: string) => Promise<void>,
|
||||
): Promise<void> {
|
||||
const port = await getAvailablePort({ port: { start: 3000, end: 30000 } });
|
||||
if (!port) {
|
||||
throw new Error("No port available for test server");
|
||||
}
|
||||
|
||||
const app = await startRestServer(port, new MemoryStorage());
|
||||
const app = await startRestServer(PORT, new MemoryStorage());
|
||||
try {
|
||||
await body(`http://localhost:${port}`);
|
||||
await body(`http://localhost:${PORT}`);
|
||||
} finally {
|
||||
await app.close();
|
||||
}
|
Loading…
Reference in a new issue