polystore 0.15.13 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +82 -0
- package/index.js +809 -0
- package/package.json +20 -24
- package/readme.md +197 -39
- package/src/clients/Client.js +0 -7
- package/src/clients/api.js +0 -34
- package/src/clients/cloudflare.js +0 -56
- package/src/clients/cookie.js +0 -51
- package/src/clients/etcd.js +0 -29
- package/src/clients/file.js +0 -70
- package/src/clients/folder.js +0 -49
- package/src/clients/forage.js +0 -29
- package/src/clients/index.js +0 -25
- package/src/clients/level.js +0 -40
- package/src/clients/memory.js +0 -19
- package/src/clients/redis.js +0 -51
- package/src/clients/storage.js +0 -25
- package/src/index.d.ts +0 -209
- package/src/index.js +0 -278
- package/src/server.js +0 -81
- package/src/utils.js +0 -47
package/src/index.js
DELETED
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
import clients from "./clients/index.js";
|
|
2
|
-
import { createId, parse, unix } from "./utils.js";
|
|
3
|
-
|
|
4
|
-
class Store {
|
|
5
|
-
PREFIX = "";
|
|
6
|
-
|
|
7
|
-
constructor(clientPromise) {
|
|
8
|
-
this.promise = Promise.resolve(clientPromise).then(async (client) => {
|
|
9
|
-
this.client = this.#find(client);
|
|
10
|
-
this.#validate(this.client);
|
|
11
|
-
this.promise = null;
|
|
12
|
-
await this.client.promise;
|
|
13
|
-
return client;
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
#find(store) {
|
|
18
|
-
// Already a fully compliant KV store
|
|
19
|
-
if (store instanceof Store) return store.client;
|
|
20
|
-
|
|
21
|
-
// One of the supported ones, so we receive an instance and
|
|
22
|
-
// wrap it with the client wrapper
|
|
23
|
-
for (let client of Object.values(clients)) {
|
|
24
|
-
if (client.test && client.test(store)) {
|
|
25
|
-
return new client(store);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// We get passed a class
|
|
30
|
-
if (
|
|
31
|
-
typeof store === "function" &&
|
|
32
|
-
/^class\s/.test(Function.prototype.toString.call(store))
|
|
33
|
-
) {
|
|
34
|
-
return new store();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// A raw one, we just receive the single instance to use directly
|
|
38
|
-
return store;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
#validate(client) {
|
|
42
|
-
if (!client) throw new Error("No client received");
|
|
43
|
-
if (!client.set || !client.get || !client.iterate) {
|
|
44
|
-
throw new Error("Client should have .get(), .set() and .iterate()");
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (!client.EXPIRES) {
|
|
48
|
-
for (let method of ["has", "keys", "values"]) {
|
|
49
|
-
if (client[method]) {
|
|
50
|
-
throw new Error(
|
|
51
|
-
`You can only define client.${method}() when the client manages the expiration.`,
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Check if the given data is fresh or not; if
|
|
59
|
-
#isFresh(data, key) {
|
|
60
|
-
// Should never happen, but COULD happen; schedule it for
|
|
61
|
-
// removal and mark it as stale
|
|
62
|
-
if (!data || !data.value || typeof data !== "object") {
|
|
63
|
-
if (key) this.del(key);
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// It never expires, so keep it
|
|
68
|
-
if (data.expires === null) return true;
|
|
69
|
-
|
|
70
|
-
// It's fresh, keep it
|
|
71
|
-
if (data.expires > Date.now()) return true;
|
|
72
|
-
|
|
73
|
-
// It's expired, remove it
|
|
74
|
-
if (key) this.del(key);
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async add(value, options = {}) {
|
|
79
|
-
await this.promise;
|
|
80
|
-
let expires = parse(options.expire ?? options.expires);
|
|
81
|
-
|
|
82
|
-
// Use the underlying one from the client if found
|
|
83
|
-
if (this.client.add) {
|
|
84
|
-
if (this.client.EXPIRES) {
|
|
85
|
-
return await this.client.add(this.PREFIX, value, { expires });
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// In the data we need the timestamp since we need it "absolute":
|
|
89
|
-
expires = unix(expires);
|
|
90
|
-
const key = await this.client.add(this.PREFIX, { expires, value });
|
|
91
|
-
return key;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const key = createId();
|
|
95
|
-
return this.set(key, value, { expires });
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
async set(key, value, options = {}) {
|
|
99
|
-
await this.promise;
|
|
100
|
-
const id = this.PREFIX + key;
|
|
101
|
-
let expires = parse(options.expire ?? options.expires);
|
|
102
|
-
|
|
103
|
-
// Quick delete
|
|
104
|
-
if (value === null || (typeof expires === "number" && expires <= 0)) {
|
|
105
|
-
return this.del(id);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// The client manages the expiration, so let it manage it
|
|
109
|
-
if (this.client.EXPIRES) {
|
|
110
|
-
await this.client.set(id, value, { expires });
|
|
111
|
-
return key;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// In the data we need the timestamp since we need it "absolute":
|
|
115
|
-
expires = unix(expires);
|
|
116
|
-
await this.client.set(id, { expires, value });
|
|
117
|
-
return key;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
async get(key) {
|
|
121
|
-
await this.promise;
|
|
122
|
-
const id = this.PREFIX + key;
|
|
123
|
-
|
|
124
|
-
const data = (await this.client.get(id)) ?? null;
|
|
125
|
-
|
|
126
|
-
// No value; nothing to do/check
|
|
127
|
-
if (data === null) return null;
|
|
128
|
-
|
|
129
|
-
// The client already managed expiration and there's STILL some data,
|
|
130
|
-
// so we can assume it's the raw user data
|
|
131
|
-
if (this.client.EXPIRES) return data;
|
|
132
|
-
|
|
133
|
-
if (!this.#isFresh(data, key)) return null;
|
|
134
|
-
return data.value;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async has(key) {
|
|
138
|
-
await this.promise;
|
|
139
|
-
const id = this.PREFIX + key;
|
|
140
|
-
|
|
141
|
-
if (this.client.has) {
|
|
142
|
-
return this.client.has(id);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return (await this.get(key)) !== null;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async del(key) {
|
|
149
|
-
await this.promise;
|
|
150
|
-
const id = this.PREFIX + key;
|
|
151
|
-
|
|
152
|
-
if (this.client.del) {
|
|
153
|
-
await this.client.del(id);
|
|
154
|
-
return key;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
await this.client.set(id, null, { expires: 0 });
|
|
158
|
-
return key;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
async *[Symbol.asyncIterator]() {
|
|
162
|
-
await this.promise;
|
|
163
|
-
|
|
164
|
-
for await (const [name, data] of this.client.iterate(this.PREFIX)) {
|
|
165
|
-
const key = name.slice(this.PREFIX.length);
|
|
166
|
-
if (this.client.EXPIRES) {
|
|
167
|
-
yield [key, data];
|
|
168
|
-
} else if (this.#isFresh(data, key)) {
|
|
169
|
-
yield [key, data.value];
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
async entries() {
|
|
175
|
-
await this.promise;
|
|
176
|
-
|
|
177
|
-
// Cut the key to size
|
|
178
|
-
const trim = (key) => key.slice(this.PREFIX.length);
|
|
179
|
-
|
|
180
|
-
let list = [];
|
|
181
|
-
if (this.client.entries) {
|
|
182
|
-
const entries = await this.client.entries(this.PREFIX);
|
|
183
|
-
list = entries.map(([key, value]) => [trim(key), value]);
|
|
184
|
-
} else {
|
|
185
|
-
for await (const [key, value] of this.client.iterate(this.PREFIX)) {
|
|
186
|
-
list.push([trim(key), value]);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// The client already manages the expiration, so we can assume
|
|
191
|
-
// that at this point, all entries are not-expired
|
|
192
|
-
if (this.client.EXPIRES) return list;
|
|
193
|
-
|
|
194
|
-
// We need to do manual expiration checking
|
|
195
|
-
return list
|
|
196
|
-
.filter(([key, data]) => this.#isFresh(data, key))
|
|
197
|
-
.map(([key, data]) => [key, data.value]);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
async keys() {
|
|
201
|
-
await this.promise;
|
|
202
|
-
|
|
203
|
-
if (this.client.keys) {
|
|
204
|
-
const list = await this.client.keys(this.PREFIX);
|
|
205
|
-
if (!this.PREFIX) return list;
|
|
206
|
-
return list.map((k) => k.slice(this.PREFIX.length));
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const entries = await this.entries();
|
|
210
|
-
return entries.map((e) => e[0]);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
async values() {
|
|
214
|
-
await this.promise;
|
|
215
|
-
|
|
216
|
-
if (this.client.values) {
|
|
217
|
-
const list = this.client.values(this.PREFIX);
|
|
218
|
-
if (this.client.EXPIRES) return list;
|
|
219
|
-
return list
|
|
220
|
-
.filter((data) => this.#isFresh(data))
|
|
221
|
-
.map((data) => data.value);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const entries = await this.entries();
|
|
225
|
-
return entries.map((e) => e[1]);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
async all() {
|
|
229
|
-
await this.promise;
|
|
230
|
-
|
|
231
|
-
if (this.client.all) {
|
|
232
|
-
const obj = await this.client.all(this.PREFIX);
|
|
233
|
-
if (!this.PREFIX) return obj;
|
|
234
|
-
const all = {};
|
|
235
|
-
for (let key in obj) {
|
|
236
|
-
all[key.slice(this.PREFIX.length)] = obj[key];
|
|
237
|
-
}
|
|
238
|
-
return all;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const entries = await this.entries();
|
|
242
|
-
return Object.fromEntries(entries);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
async clear() {
|
|
246
|
-
await this.promise;
|
|
247
|
-
|
|
248
|
-
if (!this.PREFIX && this.client.clearAll) {
|
|
249
|
-
return this.client.clearAll();
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (this.client.clear) {
|
|
253
|
-
return this.client.clear(this.PREFIX);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const keys = await this.keys();
|
|
257
|
-
// Note: this gives trouble of concurrent deletes in the FS
|
|
258
|
-
await Promise.all(keys.map((key) => this.del(key)));
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
prefix(prefix = "") {
|
|
262
|
-
const store = new Store(
|
|
263
|
-
Promise.resolve(this.promise).then(() => this.client),
|
|
264
|
-
);
|
|
265
|
-
store.PREFIX = this.PREFIX + prefix;
|
|
266
|
-
return store;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
async close() {
|
|
270
|
-
await this.promise;
|
|
271
|
-
|
|
272
|
-
if (this.client.close) {
|
|
273
|
-
return this.client.close();
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
export default (client) => new Store(client);
|
package/src/server.js
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
// This is an example server implementation of the HTTP library!
|
|
2
|
-
import http from "node:http";
|
|
3
|
-
import kv from "./index.js";
|
|
4
|
-
|
|
5
|
-
// Add/remove the key whether you want the API to be behind a key
|
|
6
|
-
const key = null;
|
|
7
|
-
// const key = 'MY-SECRET-KEY';
|
|
8
|
-
|
|
9
|
-
// Modify this to use any sub-store as desired. It's nice
|
|
10
|
-
// to use polystore itself for the polystore server library!'
|
|
11
|
-
const store = kv(new Map());
|
|
12
|
-
|
|
13
|
-
// Some reply helpers
|
|
14
|
-
const notFound = () => new Response(null, { status: 404 });
|
|
15
|
-
const sendJson = (data, status = 200) => {
|
|
16
|
-
const body = JSON.stringify(data);
|
|
17
|
-
const headers = { "content-type": "application/json" };
|
|
18
|
-
return new Response(body, { status, headers });
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
async function fetch({ method, url, body }) {
|
|
22
|
-
method = method.toLowerCase();
|
|
23
|
-
url = new URL(url);
|
|
24
|
-
let [, id] = url.pathname.split("/");
|
|
25
|
-
id = decodeURIComponent(id);
|
|
26
|
-
const expires = Number(url.searchParams.get("expires")) || null;
|
|
27
|
-
const prefix = url.searchParams.get("prefix") || null;
|
|
28
|
-
|
|
29
|
-
let local = store;
|
|
30
|
-
if (prefix) local = store.prefix(prefix);
|
|
31
|
-
|
|
32
|
-
if (method === "get") {
|
|
33
|
-
if (id === "ping") return new Response(null, { status: 200 });
|
|
34
|
-
if (!id) return sendJson(await local.all());
|
|
35
|
-
const data = await local.get(id);
|
|
36
|
-
if (data === null) return notFound();
|
|
37
|
-
return sendJson(data);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (method === "put") {
|
|
41
|
-
if (!id) return notFound();
|
|
42
|
-
const data = await new Response(body).json();
|
|
43
|
-
if (!data) return notFound();
|
|
44
|
-
await local.set(id, data, { expires });
|
|
45
|
-
return sendJson(id);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (method === "delete" && id) {
|
|
49
|
-
await local.del(id);
|
|
50
|
-
return sendJson(id);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return notFound();
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// http or express server-like handler:
|
|
57
|
-
async function server(req, res) {
|
|
58
|
-
// Secure it behind a key (optional)
|
|
59
|
-
if (key && req.headers.get("x-api-key") !== key) return res.send(401);
|
|
60
|
-
|
|
61
|
-
const url = new URL(req.url, "http://localhost:3000/").href;
|
|
62
|
-
const reply = await fetch({ ...req, url });
|
|
63
|
-
res.writeHead(reply.status, null, reply.headers || {});
|
|
64
|
-
if (reply.body) res.write(reply.body);
|
|
65
|
-
res.end();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function start(port = 3000) {
|
|
69
|
-
return new Promise((resolve, reject) => {
|
|
70
|
-
const server = http.createServer(server);
|
|
71
|
-
server.on("clientError", (error, socket) => {
|
|
72
|
-
reject(error);
|
|
73
|
-
socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
|
|
74
|
-
});
|
|
75
|
-
server.listen(port, resolve);
|
|
76
|
-
return () => server.close();
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export { fetch, server, start };
|
|
81
|
-
export default { fetch, server, start };
|
package/src/utils.js
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
const times = /(-?(?:\d+\.?\d*|\d*\.?\d+)(?:e[-+]?\d+)?)\s*([\p{L}]*)/iu;
|
|
2
|
-
|
|
3
|
-
parse.millisecond = parse.ms = 0.001;
|
|
4
|
-
parse.second = parse.sec = parse.s = parse[""] = 1;
|
|
5
|
-
parse.minute = parse.min = parse.m = parse.s * 60;
|
|
6
|
-
parse.hour = parse.hr = parse.h = parse.m * 60;
|
|
7
|
-
parse.day = parse.d = parse.h * 24;
|
|
8
|
-
parse.week = parse.wk = parse.w = parse.d * 7;
|
|
9
|
-
parse.year = parse.yr = parse.y = parse.d * 365.25;
|
|
10
|
-
parse.month = parse.b = parse.y / 12;
|
|
11
|
-
|
|
12
|
-
// Returns the time in seconds
|
|
13
|
-
export function parse(str) {
|
|
14
|
-
if (str === null || str === undefined) return null;
|
|
15
|
-
if (typeof str === "number") return str;
|
|
16
|
-
// ignore commas/placeholders
|
|
17
|
-
str = str.toLowerCase().replace(/[,_]/g, "");
|
|
18
|
-
let [_, value, units] = times.exec(str) || [];
|
|
19
|
-
if (!units) return null;
|
|
20
|
-
const unitValue = parse[units] || parse[units.replace(/s$/, "")];
|
|
21
|
-
if (!unitValue) return null;
|
|
22
|
-
const result = unitValue * parseFloat(value, 10);
|
|
23
|
-
return Math.abs(Math.round(result * 1000) / 1000);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// "nanoid" imported manually
|
|
27
|
-
// Something about improved GZIP performance with this string
|
|
28
|
-
const urlAlphabet =
|
|
29
|
-
"useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict";
|
|
30
|
-
|
|
31
|
-
export function createId() {
|
|
32
|
-
let size = 24;
|
|
33
|
-
let id = "";
|
|
34
|
-
let bytes = crypto.getRandomValues(new Uint8Array(size));
|
|
35
|
-
while (size--) {
|
|
36
|
-
// Using the bitwise AND operator to "cap" the value of
|
|
37
|
-
// the random byte from 255 to 63, in that way we can make sure
|
|
38
|
-
// that the value will be a valid index for the "chars" string.
|
|
39
|
-
id += urlAlphabet[bytes[size] & 61];
|
|
40
|
-
}
|
|
41
|
-
return id;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function unix(expires) {
|
|
45
|
-
const now = new Date().getTime();
|
|
46
|
-
return expires === null ? null : now + expires * 1000;
|
|
47
|
-
}
|