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/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
- }