diff --git a/.gitignore b/.gitignore
index d69a4c0..8b7abdc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
deno.d.ts
.vscode
.local
+.output
+web/*.js
diff --git a/README.md b/README.md
index 90933e1..e623b0c 100644
--- a/README.md
+++ b/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
+
+```
+
+## 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
+```
diff --git a/cli.ts b/cli.ts
new file mode 100755
index 0000000..2aeb3f6
--- /dev/null
+++ b/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`);
+}
diff --git a/deps.server.ts b/deps.server.ts
index 8572aac..bcd19be 100644
--- a/deps.server.ts
+++ b/deps.server.ts
@@ -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";
diff --git a/deps.testing.ts b/deps.testing.ts
index d69baa3..769812b 100644
--- a/deps.testing.ts
+++ b/deps.testing.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";
diff --git a/deps.ts b/deps.ts
index 15995d3..21bd81b 100644
--- a/deps.ts
+++ b/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";
diff --git a/doc/about.md b/doc/about.md
new file mode 100644
index 0000000..103f5b3
--- /dev/null
+++ b/doc/about.md
@@ -0,0 +1,3 @@
+## About
+
+Javascript/Typescript persistent storage, with key-value stores as foundation.
diff --git a/doc/import.md b/doc/import.md
new file mode 100644
index 0000000..c288c49
--- /dev/null
+++ b/doc/import.md
@@ -0,0 +1,15 @@
+## Import
+
+In deno:
+
+```typescript
+import { getLocalStorage } from "https://js.thunderk.net/storage/mod.ts";
+```
+
+In browser:
+
+```html
+
+```
diff --git a/doc/index b/doc/index
new file mode 100644
index 0000000..b1dffcc
--- /dev/null
+++ b/doc/index
@@ -0,0 +1,3 @@
+about
+import
+use
diff --git a/doc/use.md b/doc/use.md
new file mode 100644
index 0000000..41e6d27
--- /dev/null
+++ b/doc/use.md
@@ -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
+```
diff --git a/mod.ts b/mod.ts
index 81ed16f..a975a96 100644
--- a/mod.ts
+++ b/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();
-}
diff --git a/server.ts b/server.ts
deleted file mode 100755
index 1ee6be1..0000000
--- a/server.ts
+++ /dev/null
@@ -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;
-};
-
-/**
- * Start a server compliant with RestRemoteStorage
- */
-export function startRestServer(
- port: number,
- storage: KeyValueStorage,
-): Promise {
- 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`);
-}
diff --git a/basic.test.ts b/src/basic.test.ts
similarity index 100%
rename from basic.test.ts
rename to src/basic.test.ts
diff --git a/basic.ts b/src/basic.ts
similarity index 98%
rename from basic.ts
rename to src/basic.ts
index 6a0e261..dd5035e 100644
--- a/basic.ts
+++ b/src/basic.ts
@@ -1,4 +1,4 @@
-import { uuid1 } from "./deps.ts";
+import { uuid1 } from "../deps.ts";
/**
* Standard interface for the simplest key-value store
diff --git a/local.test.ts b/src/local.test.ts
similarity index 100%
rename from local.test.ts
rename to src/local.test.ts
diff --git a/local.ts b/src/local.ts
similarity index 100%
rename from local.ts
rename to src/local.ts
diff --git a/mod.test.ts b/src/main.test.ts
similarity index 99%
rename from mod.test.ts
rename to src/main.test.ts
index f72b196..8f5ccce 100644
--- a/mod.test.ts
+++ b/src/main.test.ts
@@ -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";
diff --git a/src/main.ts b/src/main.ts
new file mode 100644
index 0000000..81ed16f
--- /dev/null
+++ b/src/main.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();
+}
diff --git a/remote.test.ts b/src/remote.test.ts
similarity index 100%
rename from remote.test.ts
rename to src/remote.test.ts
diff --git a/remote.ts b/src/remote.ts
similarity index 100%
rename from remote.ts
rename to src/remote.ts
diff --git a/src/server.ts b/src/server.ts
new file mode 100644
index 0000000..3b636b7
--- /dev/null
+++ b/src/server.ts
@@ -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;
+};
+
+/**
+ * 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;
+ },
+ };
+}
diff --git a/testing.ts b/src/testing.ts
similarity index 77%
rename from testing.ts
rename to src/testing.ts
index 0b17174..1f99510 100644
--- a/testing.ts
+++ b/src/testing.ts
@@ -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,
): Promise {
- 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();
}