acrylblends 0.8.1 → 0.9.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/dist/index.d.ts +2 -0
- package/dist/index.js +83 -7
- package/dist/index.js.map +1 -1
- package/lib/audit-BXnYsLge.d.ts +58 -0
- package/lib/cell-ChU41DZD.d.ts +32 -0
- package/lib/client-modules-CLvWOjbd.js +267 -0
- package/lib/connection-ZLEso7XL.d.ts +25 -0
- package/lib/conninfo.d.ts +1 -23
- package/lib/cordis/db-sqlite.d.ts +56 -0
- package/lib/cordis/db-sqlite.js +58 -0
- package/lib/cordis/db.d.ts +8 -0
- package/lib/cordis/db.js +1 -0
- package/lib/cordis/door.d.ts +159 -0
- package/lib/cordis/door.js +757 -0
- package/lib/cordis/inspect.d.ts +77 -0
- package/lib/cordis/inspect.js +149 -0
- package/lib/cordis/kv-db.d.ts +16 -0
- package/lib/cordis/kv-db.js +18 -0
- package/lib/cordis/kv-json.d.ts +16 -0
- package/lib/cordis/kv-json.js +44 -0
- package/lib/cordis/kv.d.ts +8 -0
- package/lib/cordis/kv.js +1 -0
- package/lib/cordis/node-server.d.ts +45 -0
- package/lib/cordis/node-server.js +93 -0
- package/lib/cordis/router-http.d.ts +56 -0
- package/lib/cordis/router-http.js +117 -0
- package/lib/cordis/router.d.ts +8 -0
- package/lib/cordis/router.js +7 -0
- package/lib/cordis/state/react.d.ts +6 -0
- package/lib/cordis/state/react.js +11 -0
- package/lib/cordis/state.d.ts +33 -0
- package/lib/cordis/state.js +163 -0
- package/lib/core-CaY8XDZ6.js +19 -0
- package/lib/database-DBHdwefi.d.ts +44 -0
- package/lib/db/conformance.d.ts +11 -0
- package/lib/db/conformance.js +267 -0
- package/lib/db/sqlite.d.ts +1 -1
- package/lib/db/sqlite.js +1 -196
- package/lib/db.d.ts +2 -1
- package/lib/{src-0hI7M7cN.js → deprecated-BP4fPt38.js} +0 -17
- package/lib/door/host.d.ts +24 -0
- package/lib/door/host.js +2 -0
- package/lib/door/page.d.ts +105 -0
- package/lib/door/page.js +1030 -0
- package/lib/door/protocol.d.ts +2 -0
- package/lib/door/protocol.js +197 -0
- package/lib/error-t4N2js2I.d.ts +8 -0
- package/lib/etag-DaKMOjty.js +135 -0
- package/lib/etag.js +1 -134
- package/lib/helpers-CvTauQT5.js +5 -0
- package/lib/http-exception-kpGJkmpi.d.ts +57 -0
- package/lib/http-exception.d.ts +1 -56
- package/lib/{index-BLTYsyc1.d.ts → index-BnGgRKTv.d.ts} +3 -39
- package/lib/index-BqVQkTnu.d.ts +98 -0
- package/lib/index-Dmst2QCQ.d.ts +44 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.js +2 -1
- package/lib/jwk-LkzeoaU8.js +117 -0
- package/lib/jwk.js +1 -116
- package/lib/jwt-CskdhEW9.js +115 -0
- package/lib/jwt.js +2 -114
- package/lib/kv/conformance.d.ts +13 -0
- package/lib/kv/conformance.js +301 -0
- package/lib/kv/db.d.ts +2 -2
- package/lib/kv/json.d.ts +1 -1
- package/lib/kv-9f-AlRKN.d.ts +39 -0
- package/lib/kv.d.ts +2 -1
- package/lib/language-D9a4zqvn.js +198 -0
- package/lib/language.js +1 -197
- package/lib/middleware-CPQ8himN.js +12 -0
- package/lib/node-server/serve-static.d.ts +0 -1
- package/lib/node-server/serve-static.js +1 -1
- package/lib/node-server.d.ts +1 -7
- package/lib/node-server.js +3 -1586
- package/lib/open-CMhnJODn.js +197 -0
- package/lib/protocol-BizVt4J6.d.ts +144 -0
- package/lib/request-id-CKVvHOO1.js +34 -0
- package/lib/request-id.js +1 -33
- package/lib/secure-headers-mP3j6Hm6.js +163 -0
- package/lib/secure-headers.js +1 -162
- package/lib/{serve-static-B849HlX2.js → serve-static-3uo_9rjF.js} +1 -4
- package/lib/server-DvgMGtzo.js +1323 -0
- package/lib/snapshot-text-D3NspDyT.js +338 -0
- package/lib/src-CwABFr6r.js +328 -0
- package/lib/timeout.d.ts +1 -1
- package/lib/timing-Cky7C1Jf.js +183 -0
- package/lib/timing.js +1 -182
- package/package.json +89 -1
- package/plugins/acryl-db/plugin.json +6 -1
- package/plugins/acryl-db-sqlite/plugin.json +4 -0
- package/plugins/acryl-db-sqlite/src/facade/node.ts +1 -1
- package/plugins/acryl-db-sqlite/src/index.ts +2 -2
- package/plugins/acryl-db-sqlite/src/open.ts +2 -2
- package/plugins/acryl-door/plugin.json +4 -1
- package/plugins/acryl-door/src/host/http.ts +2 -2
- package/plugins/acryl-door/src/host/mcp-routes.ts +1 -1
- package/plugins/acryl-door/src/host/operator-routes.ts +1 -1
- package/plugins/acryl-door/src/host/page-routes.ts +3 -3
- package/plugins/acryl-door/src/index.ts +2 -2
- package/plugins/acryl-inspect/plugin.json +4 -1
- package/plugins/acryl-inspect/src/service.ts +2 -2
- package/plugins/acryl-kv/plugin.json +6 -1
- package/plugins/acryl-kv-db/plugin.json +4 -0
- package/plugins/acryl-kv-db/src/index.ts +1 -1
- package/plugins/acryl-kv-db/src/open.ts +3 -3
- package/plugins/acryl-kv-json/plugin.json +4 -0
- package/plugins/acryl-kv-json/src/index.ts +1 -1
- package/plugins/acryl-kv-json/src/open.ts +2 -2
- package/plugins/acryl-node-server/plugin.json +5 -1
- package/plugins/acryl-node-server/src/adapter/conninfo.ts +1 -1
- package/plugins/acryl-node-server/src/adapter/early-hints.ts +1 -1
- package/plugins/acryl-node-server/src/adapter/serve-static.ts +2 -2
- package/plugins/acryl-node-server/src/adapter/websocket.ts +2 -2
- package/plugins/acryl-node-server/src/client-modules.ts +2 -2
- package/plugins/acryl-node-server/src/index.ts +1 -1
- package/plugins/acryl-router/plugin.json +11 -1
- package/plugins/acryl-router/src/connection.d.ts +25 -0
- package/plugins/acryl-router/src/core/app-base.d.ts +215 -0
- package/plugins/acryl-router/src/core/app.d.ts +19 -0
- package/plugins/acryl-router/src/core/client/client.d.ts +4 -0
- package/plugins/acryl-router/src/core/client/fetch-result-please.d.ts +35 -0
- package/plugins/acryl-router/src/core/client/index.d.ts +7 -0
- package/plugins/acryl-router/src/core/client/types.d.ts +229 -0
- package/plugins/acryl-router/src/core/client/utils.d.ts +18 -0
- package/plugins/acryl-router/src/core/compose.d.ts +14 -0
- package/plugins/acryl-router/src/core/context.d.ts +443 -0
- package/plugins/acryl-router/src/core/helper/accepts/accepts.d.ts +30 -0
- package/plugins/acryl-router/src/core/helper/accepts/index.d.ts +5 -0
- package/plugins/acryl-router/src/core/helper/conninfo/index.d.ts +5 -0
- package/plugins/acryl-router/src/core/helper/conninfo/types.d.ts +36 -0
- package/plugins/acryl-router/src/core/helper/cookie/index.d.ts +25 -0
- package/plugins/acryl-router/src/core/helper/css/common.d.ts +59 -0
- package/plugins/acryl-router/src/core/helper/css/index.d.ts +80 -0
- package/plugins/acryl-router/src/core/helper/dev/index.d.ts +20 -0
- package/plugins/acryl-router/src/core/helper/factory/index.d.ts +82 -0
- package/plugins/acryl-router/src/core/helper/html/index.d.ts +8 -0
- package/plugins/acryl-router/src/core/helper/proxy/index.d.ts +73 -0
- package/plugins/acryl-router/src/core/helper/route/index.d.ts +75 -0
- package/plugins/acryl-router/src/core/helper/ssg/index.d.ts +7 -0
- package/plugins/acryl-router/src/core/helper/ssg/middleware.d.ts +44 -0
- package/plugins/acryl-router/src/core/helper/ssg/plugins.d.ts +27 -0
- package/plugins/acryl-router/src/core/helper/ssg/ssg.d.ts +91 -0
- package/plugins/acryl-router/src/core/helper/ssg/utils.d.ts +16 -0
- package/plugins/acryl-router/src/core/helper/streaming/index.d.ts +8 -0
- package/plugins/acryl-router/src/core/helper/streaming/sse.d.ts +13 -0
- package/plugins/acryl-router/src/core/helper/streaming/stream.d.ts +3 -0
- package/plugins/acryl-router/src/core/helper/streaming/text.d.ts +3 -0
- package/plugins/acryl-router/src/core/helper/streaming/utils.d.ts +1 -0
- package/plugins/acryl-router/src/core/helper/testing/index.d.ts +13 -0
- package/plugins/acryl-router/src/core/helper/websocket/index.d.ts +70 -0
- package/plugins/acryl-router/src/core/http-exception.d.ts +58 -0
- package/plugins/acryl-router/src/core/index.d.ts +37 -0
- package/plugins/acryl-router/src/core/jsx/base.d.ts +61 -0
- package/plugins/acryl-router/src/core/jsx/children.d.ts +9 -0
- package/plugins/acryl-router/src/core/jsx/components.d.ts +15 -0
- package/plugins/acryl-router/src/core/jsx/constants.d.ts +6 -0
- package/plugins/acryl-router/src/core/jsx/context.d.ts +51 -0
- package/plugins/acryl-router/src/core/jsx/dom/components.d.ts +10 -0
- package/plugins/acryl-router/src/core/jsx/dom/context.d.ts +3 -0
- package/plugins/acryl-router/src/core/jsx/dom/css.d.ts +83 -0
- package/plugins/acryl-router/src/core/jsx/dom/hooks/index.d.ts +39 -0
- package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.d.ts +16 -0
- package/plugins/acryl-router/src/core/jsx/dom/jsx-dev-runtime.d.ts +8 -0
- package/plugins/acryl-router/src/core/jsx/dom/jsx-runtime.d.ts +7 -0
- package/plugins/acryl-router/src/core/jsx/dom/render.d.ts +73 -0
- package/plugins/acryl-router/src/core/jsx/dom/utils.d.ts +1 -0
- package/plugins/acryl-router/src/core/jsx/hooks/index.d.ts +47 -0
- package/plugins/acryl-router/src/core/jsx/index.d.ts +76 -0
- package/plugins/acryl-router/src/core/jsx/intrinsic-element/common.d.ts +6 -0
- package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.d.ts +13 -0
- package/plugins/acryl-router/src/core/jsx/intrinsic-elements.d.ts +735 -0
- package/plugins/acryl-router/src/core/jsx/streaming.d.ts +36 -0
- package/plugins/acryl-router/src/core/jsx/types.d.ts +36 -0
- package/plugins/acryl-router/src/core/jsx/utils.d.ts +4 -0
- package/plugins/acryl-router/src/core/request/constants.d.ts +1 -0
- package/plugins/acryl-router/src/core/request.d.ts +306 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/index.d.ts +6 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/matcher.d.ts +7 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/node.d.ts +13 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.d.ts +17 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/router.d.ts +11 -0
- package/plugins/acryl-router/src/core/router/reg-exp-router/trie.d.ts +8 -0
- package/plugins/acryl-router/src/core/router/smart-router/index.d.ts +5 -0
- package/plugins/acryl-router/src/core/router/smart-router/router.d.ts +11 -0
- package/plugins/acryl-router/src/core/router/trie-router/index.d.ts +5 -0
- package/plugins/acryl-router/src/core/router/trie-router/node.d.ts +6 -0
- package/plugins/acryl-router/src/core/router/trie-router/router.d.ts +7 -0
- package/plugins/acryl-router/src/core/router/utils.d.ts +1 -0
- package/plugins/acryl-router/src/core/router.d.ts +97 -0
- package/plugins/acryl-router/src/core/types.d.ts +573 -0
- package/plugins/acryl-router/src/core/utils/accept.d.ts +6 -0
- package/plugins/acryl-router/src/core/utils/body.d.ts +79 -0
- package/plugins/acryl-router/src/core/utils/buffer.d.ts +17 -0
- package/plugins/acryl-router/src/core/utils/color.d.ts +21 -0
- package/plugins/acryl-router/src/core/utils/concurrent.d.ts +11 -0
- package/plugins/acryl-router/src/core/utils/constants.d.ts +4 -0
- package/plugins/acryl-router/src/core/utils/cookie.d.ts +41 -0
- package/plugins/acryl-router/src/core/utils/crypto.d.ts +15 -0
- package/plugins/acryl-router/src/core/utils/handler.d.ts +6 -0
- package/plugins/acryl-router/src/core/utils/headers.d.ts +8 -0
- package/plugins/acryl-router/src/core/utils/html.d.ts +44 -0
- package/plugins/acryl-router/src/core/utils/http-status.d.ts +32 -0
- package/plugins/acryl-router/src/core/utils/mime.d.ts +70 -0
- package/plugins/acryl-router/src/core/utils/stream.d.ts +31 -0
- package/plugins/acryl-router/src/core/utils/types.d.ts +128 -0
- package/plugins/acryl-router/src/core/utils/url.d.ts +44 -0
- package/plugins/acryl-router/src/core/validator/index.d.ts +7 -0
- package/plugins/acryl-router/src/core/validator/utils.d.ts +29 -0
- package/plugins/acryl-router/src/core/validator/validator.d.ts +16 -0
- package/plugins/acryl-router/src/helpers/index.d.ts +17 -0
- package/plugins/acryl-router/src/helpers/schema-validator.d.ts +54 -0
- package/plugins/acryl-router-http/plugin.json +4 -1
- package/plugins/acryl-router-http/src/index.ts +1 -1
- package/plugins/acryl-state/plugin.json +5 -1
- package/plugins/acryl-state/src/service.ts +3 -3
- package/plugins/tsconfig.json +32 -20
- package/docs/framework/UPSTREAM.md +0 -3
- package/docs/framework/upstream.json +0 -9
- package/lib/index-D4FAsBZu.d.ts +0 -85
- package/plugins/acryl-db/upstream.json +0 -18
- package/plugins/acryl-db-sqlite/upstream.json +0 -21
- package/plugins/acryl-door/upstream.json +0 -28
- package/plugins/acryl-inspect/upstream.json +0 -18
- package/plugins/acryl-kv/upstream.json +0 -18
- package/plugins/acryl-kv-db/upstream.json +0 -21
- package/plugins/acryl-kv-json/upstream.json +0 -22
- package/plugins/acryl-node-server/upstream.json +0 -21
- package/plugins/acryl-router/upstream.json +0 -20
- package/plugins/acryl-router-http/upstream.json +0 -22
- package/plugins/acryl-state/NOTICE +0 -30
- package/plugins/acryl-state/upstream.json +0 -22
- package/plugins/desktop/upstream.json +0 -12
- package/plugins/desktop-web/upstream.json +0 -12
- package/templates/todo/upstream.json +0 -12
- /package/lib/{index-CTy4N7ng.d.ts → deprecated-CTy4N7ng.d.ts} +0 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
2
|
+
//#region ../../plugins/acryl-kv/src/conformance.ts
|
|
3
|
+
/**
|
|
4
|
+
* The `kv` conformance suite: what every provider of the port must do, as Vitest tests. A provider passes it to be accepted
|
|
5
|
+
* (`describeKvConformance('acryl-kv-json', open)` in its own tests), so the same cases run on a file and on SQL.
|
|
6
|
+
*/
|
|
7
|
+
function describeKvConformance(provider, open) {
|
|
8
|
+
describe(`kv conformance: ${provider}`, () => {
|
|
9
|
+
const opened = [];
|
|
10
|
+
const track = (subject) => {
|
|
11
|
+
opened.push(subject);
|
|
12
|
+
return subject;
|
|
13
|
+
};
|
|
14
|
+
const fresh = async () => track(await open()).kv;
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
await Promise.all(opened.splice(0).map((subject) => subject.close()));
|
|
17
|
+
});
|
|
18
|
+
it("round-trips null, booleans, numbers, text with unicode, arrays and nested objects", async () => {
|
|
19
|
+
const kv = await fresh();
|
|
20
|
+
for (const [index, value] of [
|
|
21
|
+
null,
|
|
22
|
+
true,
|
|
23
|
+
false,
|
|
24
|
+
0,
|
|
25
|
+
-1.5,
|
|
26
|
+
9007199254740991,
|
|
27
|
+
"",
|
|
28
|
+
"café ✓ 😀",
|
|
29
|
+
[],
|
|
30
|
+
[1, [2, [3]]],
|
|
31
|
+
{},
|
|
32
|
+
{
|
|
33
|
+
a: { b: [1, { c: null }] },
|
|
34
|
+
"ключ": "значение"
|
|
35
|
+
}
|
|
36
|
+
].entries()) {
|
|
37
|
+
await kv.set(`value/${index}`, value);
|
|
38
|
+
expect(await kv.get(`value/${index}`), JSON.stringify(value)).toEqual(value);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
it("answers get with undefined for a missing key, and overwrites on set", async () => {
|
|
42
|
+
const kv = await fresh();
|
|
43
|
+
expect(await kv.get("missing")).toBeUndefined();
|
|
44
|
+
await kv.set("k", { n: 1 });
|
|
45
|
+
await kv.set("k", { n: 2 });
|
|
46
|
+
expect(await kv.get("k")).toEqual({ n: 2 });
|
|
47
|
+
});
|
|
48
|
+
it("deletes a key, and a missing key is not an error", async () => {
|
|
49
|
+
const kv = await fresh();
|
|
50
|
+
await kv.set("k", 1);
|
|
51
|
+
await kv.delete("k");
|
|
52
|
+
expect(await kv.get("k")).toBeUndefined();
|
|
53
|
+
await expect(kv.delete("k")).resolves.toBeUndefined();
|
|
54
|
+
await expect(kv.delete("never-existed")).resolves.toBeUndefined();
|
|
55
|
+
});
|
|
56
|
+
it("stores a null value as null, apart from a missing key", async () => {
|
|
57
|
+
const kv = await fresh();
|
|
58
|
+
await kv.set("k", null);
|
|
59
|
+
expect(await kv.get("k")).toBeNull();
|
|
60
|
+
expect(await kv.list()).toEqual(["k"]);
|
|
61
|
+
});
|
|
62
|
+
it("lists keys sorted by code point, with an optional prefix", async () => {
|
|
63
|
+
const kv = await fresh();
|
|
64
|
+
for (const key of [
|
|
65
|
+
"b",
|
|
66
|
+
"a/2",
|
|
67
|
+
"a/10",
|
|
68
|
+
"a/1",
|
|
69
|
+
"ab",
|
|
70
|
+
"B",
|
|
71
|
+
"a"
|
|
72
|
+
]) await kv.set(key, key);
|
|
73
|
+
expect(await kv.list()).toEqual([
|
|
74
|
+
"B",
|
|
75
|
+
"a",
|
|
76
|
+
"a/1",
|
|
77
|
+
"a/10",
|
|
78
|
+
"a/2",
|
|
79
|
+
"ab",
|
|
80
|
+
"b"
|
|
81
|
+
]);
|
|
82
|
+
expect(await kv.list("a/")).toEqual([
|
|
83
|
+
"a/1",
|
|
84
|
+
"a/10",
|
|
85
|
+
"a/2"
|
|
86
|
+
]);
|
|
87
|
+
expect(await kv.list("a")).toEqual([
|
|
88
|
+
"a",
|
|
89
|
+
"a/1",
|
|
90
|
+
"a/10",
|
|
91
|
+
"a/2",
|
|
92
|
+
"ab"
|
|
93
|
+
]);
|
|
94
|
+
expect(await kv.list("")).toEqual([
|
|
95
|
+
"B",
|
|
96
|
+
"a",
|
|
97
|
+
"a/1",
|
|
98
|
+
"a/10",
|
|
99
|
+
"a/2",
|
|
100
|
+
"ab",
|
|
101
|
+
"b"
|
|
102
|
+
]);
|
|
103
|
+
expect(await kv.list("nothing")).toEqual([]);
|
|
104
|
+
expect(await kv.list(void 0)).toEqual(await kv.list());
|
|
105
|
+
});
|
|
106
|
+
it("orders astral characters after the rest of the basic plane, as code points", async () => {
|
|
107
|
+
const kv = await fresh();
|
|
108
|
+
for (const key of [
|
|
109
|
+
"😀",
|
|
110
|
+
"~",
|
|
111
|
+
"z",
|
|
112
|
+
""
|
|
113
|
+
]) await kv.set(key, 1);
|
|
114
|
+
expect(await kv.list()).toEqual([
|
|
115
|
+
"z",
|
|
116
|
+
"",
|
|
117
|
+
"~",
|
|
118
|
+
"😀"
|
|
119
|
+
]);
|
|
120
|
+
});
|
|
121
|
+
it("treats % _ \\ and letter case in a prefix as plain characters", async () => {
|
|
122
|
+
const kv = await fresh();
|
|
123
|
+
for (const key of [
|
|
124
|
+
"100%",
|
|
125
|
+
"100x",
|
|
126
|
+
"a_b",
|
|
127
|
+
"axb",
|
|
128
|
+
"Abc",
|
|
129
|
+
"abc",
|
|
130
|
+
"back\\slash",
|
|
131
|
+
"backXslash"
|
|
132
|
+
]) await kv.set(key, 1);
|
|
133
|
+
expect(await kv.list("100%")).toEqual(["100%"]);
|
|
134
|
+
expect(await kv.list("a_")).toEqual(["a_b"]);
|
|
135
|
+
expect(await kv.list("ab")).toEqual(["abc"]);
|
|
136
|
+
expect(await kv.list("Ab")).toEqual(["Abc"]);
|
|
137
|
+
expect(await kv.list("back\\")).toEqual(["back\\slash"]);
|
|
138
|
+
expect(await kv.list("😀")).toEqual([]);
|
|
139
|
+
});
|
|
140
|
+
it("updates a missing key from undefined, and a present one from its value, resolving the new value", async () => {
|
|
141
|
+
const kv = await fresh();
|
|
142
|
+
expect(await kv.update("n", (current) => (current ?? 0) + 1)).toBe(1);
|
|
143
|
+
expect(await kv.update("n", (current) => (current ?? 0) + 10)).toBe(11);
|
|
144
|
+
expect(await kv.get("n")).toBe(11);
|
|
145
|
+
expect(await kv.update("list", (current) => [...current ?? [], "a"])).toEqual(["a"]);
|
|
146
|
+
});
|
|
147
|
+
it("counts fifty concurrent updates to exactly fifty", async () => {
|
|
148
|
+
const kv = await fresh();
|
|
149
|
+
await Promise.all(Array.from({ length: 50 }, () => kv.update("counter", (current) => (current ?? 0) + 1)));
|
|
150
|
+
expect(await kv.get("counter")).toBe(50);
|
|
151
|
+
});
|
|
152
|
+
it("keeps updates of different keys independent, and a set between two updates in order", async () => {
|
|
153
|
+
const kv = await fresh();
|
|
154
|
+
const results = await Promise.all([
|
|
155
|
+
kv.update("a", (current) => (current ?? 0) + 1),
|
|
156
|
+
kv.set("a", 100),
|
|
157
|
+
kv.update("a", (current) => (current ?? 0) + 1),
|
|
158
|
+
kv.update("b", (current) => (current ?? 0) + 5)
|
|
159
|
+
]);
|
|
160
|
+
expect(results[0]).toBe(1);
|
|
161
|
+
expect(results[2]).toBe(101);
|
|
162
|
+
expect(await kv.get("a")).toBe(101);
|
|
163
|
+
expect(await kv.get("b")).toBe(5);
|
|
164
|
+
});
|
|
165
|
+
it("leaves the value as it was when the update callback throws, and rejects with its error", async () => {
|
|
166
|
+
const kv = await fresh();
|
|
167
|
+
await kv.set("k", "before");
|
|
168
|
+
const failure = /* @__PURE__ */ new Error("stop");
|
|
169
|
+
await expect(kv.update("k", () => {
|
|
170
|
+
throw failure;
|
|
171
|
+
})).rejects.toBe(failure);
|
|
172
|
+
expect(await kv.get("k")).toBe("before");
|
|
173
|
+
await expect(kv.update("absent", () => {
|
|
174
|
+
throw failure;
|
|
175
|
+
})).rejects.toBe(failure);
|
|
176
|
+
expect(await kv.get("absent")).toBeUndefined();
|
|
177
|
+
});
|
|
178
|
+
it("leaves the value as it was when the update returns something that is not JSON", async () => {
|
|
179
|
+
const kv = await fresh();
|
|
180
|
+
await kv.set("k", "before");
|
|
181
|
+
await expect(kv.update("k", () => void 0)).rejects.toThrow(TypeError);
|
|
182
|
+
await expect(kv.update("k", () => NaN)).rejects.toThrow(TypeError);
|
|
183
|
+
expect(await kv.get("k")).toBe("before");
|
|
184
|
+
});
|
|
185
|
+
it("rejects an update callback that returns a promise, with a message that says what to do, and keeps the value", async () => {
|
|
186
|
+
const kv = await fresh();
|
|
187
|
+
await kv.set("k", 1);
|
|
188
|
+
const asynchronous = (async () => 2);
|
|
189
|
+
await expect(kv.update("k", asynchronous)).rejects.toThrow(/synchronous/u);
|
|
190
|
+
expect(await kv.get("k")).toBe(1);
|
|
191
|
+
await kv.update("k", (current) => (current ?? 0) + 1);
|
|
192
|
+
expect(await kv.get("k")).toBe(2);
|
|
193
|
+
});
|
|
194
|
+
it("refuses an empty or non-string key on every call, and writes nothing", async () => {
|
|
195
|
+
const kv = await fresh();
|
|
196
|
+
for (const key of [
|
|
197
|
+
"",
|
|
198
|
+
1,
|
|
199
|
+
null,
|
|
200
|
+
void 0,
|
|
201
|
+
{}
|
|
202
|
+
]) {
|
|
203
|
+
await expect(kv.get(key)).rejects.toThrow(TypeError);
|
|
204
|
+
await expect(kv.set(key, 1)).rejects.toThrow(TypeError);
|
|
205
|
+
await expect(kv.update(key, () => 1)).rejects.toThrow(TypeError);
|
|
206
|
+
await expect(kv.delete(key)).rejects.toThrow(TypeError);
|
|
207
|
+
}
|
|
208
|
+
expect(await kv.list()).toEqual([]);
|
|
209
|
+
});
|
|
210
|
+
it("refuses a value that is not JSON with a TypeError, and writes nothing", async () => {
|
|
211
|
+
const kv = await fresh();
|
|
212
|
+
const circular = {};
|
|
213
|
+
circular.self = circular;
|
|
214
|
+
for (const value of [
|
|
215
|
+
void 0,
|
|
216
|
+
() => 1,
|
|
217
|
+
Symbol("s"),
|
|
218
|
+
1n,
|
|
219
|
+
NaN,
|
|
220
|
+
Number.POSITIVE_INFINITY,
|
|
221
|
+
circular,
|
|
222
|
+
{ deep: [Number.NEGATIVE_INFINITY] }
|
|
223
|
+
]) await expect(kv.set("k", value), String(value)).rejects.toThrow(TypeError);
|
|
224
|
+
expect(await kv.list()).toEqual([]);
|
|
225
|
+
await kv.set("k", "kept");
|
|
226
|
+
await expect(kv.set("k", void 0)).rejects.toThrow(TypeError);
|
|
227
|
+
expect(await kv.get("k")).toBe("kept");
|
|
228
|
+
});
|
|
229
|
+
it("hands out copies: changing what get returned, or the value after set, changes nothing stored", async () => {
|
|
230
|
+
const kv = await fresh();
|
|
231
|
+
const original = {
|
|
232
|
+
list: [1, 2],
|
|
233
|
+
nested: { n: 1 }
|
|
234
|
+
};
|
|
235
|
+
await kv.set("k", original);
|
|
236
|
+
original.list.push(3);
|
|
237
|
+
const first = await kv.get("k");
|
|
238
|
+
expect(first).toEqual({
|
|
239
|
+
list: [1, 2],
|
|
240
|
+
nested: { n: 1 }
|
|
241
|
+
});
|
|
242
|
+
first.nested.n = 99;
|
|
243
|
+
expect(await kv.get("k")).toEqual({
|
|
244
|
+
list: [1, 2],
|
|
245
|
+
nested: { n: 1 }
|
|
246
|
+
});
|
|
247
|
+
(await kv.update("k", (current) => {
|
|
248
|
+
current.list.push(4);
|
|
249
|
+
return current;
|
|
250
|
+
})).list.push(5);
|
|
251
|
+
expect(await kv.get("k")).toEqual({
|
|
252
|
+
list: [
|
|
253
|
+
1,
|
|
254
|
+
2,
|
|
255
|
+
4
|
|
256
|
+
],
|
|
257
|
+
nested: { n: 1 }
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
it("stores a value of a megabyte", async () => {
|
|
261
|
+
const kv = await fresh();
|
|
262
|
+
const big = "x".repeat(1024 * 1024);
|
|
263
|
+
await kv.set("big", { big });
|
|
264
|
+
expect((await kv.get("big"))?.big.length).toBe(big.length);
|
|
265
|
+
});
|
|
266
|
+
it("keeps what was written across close and reopen, and keeps a deleted key deleted", async () => {
|
|
267
|
+
const first = track(await open());
|
|
268
|
+
await first.kv.set("a", { n: 1 });
|
|
269
|
+
await first.kv.set("b", "two");
|
|
270
|
+
await first.kv.update("count", (current) => (current ?? 0) + 1);
|
|
271
|
+
await first.kv.set("gone", 1);
|
|
272
|
+
await first.kv.delete("gone");
|
|
273
|
+
const second = track(await first.reopen());
|
|
274
|
+
expect(await second.kv.list()).toEqual([
|
|
275
|
+
"a",
|
|
276
|
+
"b",
|
|
277
|
+
"count"
|
|
278
|
+
]);
|
|
279
|
+
expect(await second.kv.get("a")).toEqual({ n: 1 });
|
|
280
|
+
expect(await second.kv.get("count")).toBe(1);
|
|
281
|
+
expect(await second.kv.get("gone")).toBeUndefined();
|
|
282
|
+
});
|
|
283
|
+
it("rejects calls after close, and closing twice does nothing", async () => {
|
|
284
|
+
const subject = track(await open());
|
|
285
|
+
await subject.kv.set("k", 1);
|
|
286
|
+
await subject.close();
|
|
287
|
+
await subject.close();
|
|
288
|
+
await expect(subject.kv.get("k")).rejects.toThrow();
|
|
289
|
+
await expect(subject.kv.set("k", 2)).rejects.toThrow();
|
|
290
|
+
});
|
|
291
|
+
it("waits for writes still in flight when it closes", async () => {
|
|
292
|
+
const subject = track(await open());
|
|
293
|
+
const writes = Array.from({ length: 20 }, (_, index) => subject.kv.set(`k${String(index).padStart(2, "0")}`, index));
|
|
294
|
+
await subject.close();
|
|
295
|
+
await Promise.all(writes);
|
|
296
|
+
expect(await track(await subject.reopen()).kv.list()).toHaveLength(20);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
//#endregion
|
|
301
|
+
export { describeKvConformance };
|
package/lib/kv/db.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { t as Db } from "../database-DBHdwefi.js";
|
|
2
|
+
import { n as Kv } from "../kv-9f-AlRKN.js";
|
|
3
3
|
|
|
4
4
|
//#region ../../plugins/acryl-kv-db/src/open.d.ts
|
|
5
5
|
interface KvOnDbOptions {
|
package/lib/kv/json.d.ts
CHANGED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//#region ../../plugins/acryl-kv/src/kv.d.ts
|
|
2
|
+
/** `T`, except a promise: the callback of `update` is synchronous, and an async one is a type error. */
|
|
3
|
+
type NotPromise<T> = T extends PromiseLike<unknown> ? never : T;
|
|
4
|
+
/** Anything JSON can hold. */
|
|
5
|
+
type JsonValue = null | boolean | number | string | JsonValue[] | {
|
|
6
|
+
[key: string]: JsonValue;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Named JSON documents. Every call returns a promise, so a file, SQLite and Postgres answer alike.
|
|
10
|
+
*
|
|
11
|
+
* Keys are non-empty strings. Values are JSON: `undefined`, functions, symbols, bigints and non-finite numbers are refused with a
|
|
12
|
+
* `TypeError` before anything is written, and a value read back is a copy.
|
|
13
|
+
*
|
|
14
|
+
* There is no `close`: the code that opened a store closes it (see {@link KvOpened}), so a plugin cannot close the store another uses.
|
|
15
|
+
*/
|
|
16
|
+
interface Kv {
|
|
17
|
+
/** The value stored under the key, or `undefined`. */
|
|
18
|
+
get<T = JsonValue>(key: string): Promise<T | undefined>;
|
|
19
|
+
/** Store a value under the key, replacing any. Resolves once it is durable. */
|
|
20
|
+
set<T = JsonValue>(key: string, value: T): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Replace the value under the key by `change(current)`, atomically: `current` is `undefined` when the key is missing, `change` is
|
|
23
|
+
* synchronous (an `async` function is a type error, and a returned promise is rejected at run time: read what you need first, then update), and no other write to the key happens between reading it and writing the result. Resolves the new value. A `change` that
|
|
24
|
+
* throws, or returns something that is not JSON, leaves the value as it was and rejects with its error.
|
|
25
|
+
*/
|
|
26
|
+
update<T = JsonValue>(key: string, change: (current: T | undefined) => NotPromise<T>): Promise<T>;
|
|
27
|
+
/** Remove the key. A missing key is not an error. */
|
|
28
|
+
delete(key: string): Promise<void>;
|
|
29
|
+
/** The keys that start with the prefix (all of them without one), sorted by code point (the order of their UTF-8 bytes, not of UTF-16 units). */
|
|
30
|
+
list(prefix?: string): Promise<string[]>;
|
|
31
|
+
}
|
|
32
|
+
/** An open store and the one function that closes it. */
|
|
33
|
+
interface KvOpened {
|
|
34
|
+
readonly kv: Kv;
|
|
35
|
+
/** Waits for the writes in flight, then releases the storage. Calling it again does nothing; calls on `kv` after it reject. */
|
|
36
|
+
close(): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
export { NotPromise as i, Kv as n, KvOpened as r, JsonValue as t };
|
package/lib/kv.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as NotPromise, n as Kv, r as KvOpened, t as JsonValue } from "./kv-9f-AlRKN.js";
|
|
2
|
+
import { a as checkKey, c as decodeValue, i as copyKv, l as encodeValue, n as CopyKvOptions, o as codePoints, r as CopyKvResult, s as compareKeys, t as KV_SERVICE, u as runChange } from "./index-BnGgRKTv.js";
|
|
2
3
|
export { type CopyKvOptions, type CopyKvResult, type JsonValue, KV_SERVICE, type Kv, type KvOpened, type NotPromise, checkKey, codePoints, compareKeys, copyKv, decodeValue, encodeValue, runChange };
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { parseAccept } from "./utils/accept.js";
|
|
2
|
+
import { getCookie, setCookie } from "./cookie.js";
|
|
3
|
+
//#region ../../plugins/acryl-router/src/core/middleware/language/language.ts
|
|
4
|
+
const DEFAULT_OPTIONS = {
|
|
5
|
+
order: [
|
|
6
|
+
"querystring",
|
|
7
|
+
"cookie",
|
|
8
|
+
"header"
|
|
9
|
+
],
|
|
10
|
+
lookupQueryString: "lang",
|
|
11
|
+
lookupCookie: "language",
|
|
12
|
+
lookupFromHeaderKey: "accept-language",
|
|
13
|
+
lookupFromPathIndex: 0,
|
|
14
|
+
caches: ["cookie"],
|
|
15
|
+
ignoreCase: true,
|
|
16
|
+
fallbackLanguage: "en",
|
|
17
|
+
supportedLanguages: ["en"],
|
|
18
|
+
cookieOptions: {
|
|
19
|
+
sameSite: "Strict",
|
|
20
|
+
secure: true,
|
|
21
|
+
maxAge: 365 * 24 * 60 * 60,
|
|
22
|
+
httpOnly: true
|
|
23
|
+
},
|
|
24
|
+
debug: false
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Parse Accept-Language header values with quality scores
|
|
28
|
+
* @param header Accept-Language header string
|
|
29
|
+
* @returns Array of parsed languages with quality scores
|
|
30
|
+
*/
|
|
31
|
+
function parseAcceptLanguage(header) {
|
|
32
|
+
return parseAccept(header).map(({ type, q }) => ({
|
|
33
|
+
lang: type,
|
|
34
|
+
q
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Validate and normalize language codes
|
|
39
|
+
* @param lang Language code to normalize
|
|
40
|
+
* @param options Detector options
|
|
41
|
+
* @returns Normalized language code or undefined
|
|
42
|
+
*/
|
|
43
|
+
const normalizeLanguage = (lang, options) => {
|
|
44
|
+
if (!lang) return;
|
|
45
|
+
try {
|
|
46
|
+
let normalizedLang = lang.trim();
|
|
47
|
+
if (options.convertDetectedLanguage) normalizedLang = options.convertDetectedLanguage(normalizedLang);
|
|
48
|
+
const compLang = options.ignoreCase ? normalizedLang.toLowerCase() : normalizedLang;
|
|
49
|
+
const compSupported = options.supportedLanguages.map((l) => options.ignoreCase ? l.toLowerCase() : l);
|
|
50
|
+
const exactIndex = compSupported.indexOf(compLang);
|
|
51
|
+
if (exactIndex !== -1) return options.supportedLanguages[exactIndex];
|
|
52
|
+
let longestMatchIndex = -1;
|
|
53
|
+
let longestMatchLength = -1;
|
|
54
|
+
for (let i = 0; i < compSupported.length; i++) {
|
|
55
|
+
const candidate = compSupported[i];
|
|
56
|
+
if (candidate.length < compLang.length && candidate.length > longestMatchLength && compLang.startsWith(candidate) && compLang[candidate.length] === "-") {
|
|
57
|
+
longestMatchIndex = i;
|
|
58
|
+
longestMatchLength = candidate.length;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (longestMatchIndex !== -1) return options.supportedLanguages[longestMatchIndex];
|
|
62
|
+
return;
|
|
63
|
+
} catch {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Detects language from query parameter
|
|
69
|
+
*/
|
|
70
|
+
const detectFromQuery = (c, options) => {
|
|
71
|
+
const query = c.req.query(options.lookupQueryString);
|
|
72
|
+
return normalizeLanguage(query, options);
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Detects language from cookie
|
|
76
|
+
*/
|
|
77
|
+
const detectFromCookie = (c, options) => {
|
|
78
|
+
const cookie = getCookie(c, options.lookupCookie);
|
|
79
|
+
return normalizeLanguage(cookie, options);
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Detects language from Accept-Language header
|
|
83
|
+
*/
|
|
84
|
+
function detectFromHeader(c, options) {
|
|
85
|
+
try {
|
|
86
|
+
const acceptLanguage = c.req.header(options.lookupFromHeaderKey);
|
|
87
|
+
if (!acceptLanguage) return;
|
|
88
|
+
const languages = parseAcceptLanguage(acceptLanguage);
|
|
89
|
+
for (const { lang, q } of languages) {
|
|
90
|
+
if (q === 0) continue;
|
|
91
|
+
const normalizedLang = normalizeLanguage(lang, options);
|
|
92
|
+
if (normalizedLang) return normalizedLang;
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
} catch {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Detects language from URL path
|
|
101
|
+
*/
|
|
102
|
+
function detectFromPath(c, options) {
|
|
103
|
+
const langSegment = new URL(c.req.url).pathname.split("/").filter(Boolean)[options.lookupFromPathIndex];
|
|
104
|
+
return normalizeLanguage(langSegment, options);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Collection of all language detection strategies
|
|
108
|
+
*/
|
|
109
|
+
const detectors = {
|
|
110
|
+
querystring: detectFromQuery,
|
|
111
|
+
cookie: detectFromCookie,
|
|
112
|
+
header: detectFromHeader,
|
|
113
|
+
path: detectFromPath
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Validate detector options
|
|
117
|
+
* @param options Detector options to validate
|
|
118
|
+
* @throws Error if options are invalid
|
|
119
|
+
*/
|
|
120
|
+
function validateOptions(options) {
|
|
121
|
+
if (!options.supportedLanguages.includes(options.fallbackLanguage)) throw new Error("Fallback language must be included in supported languages");
|
|
122
|
+
if (options.lookupFromPathIndex < 0) throw new Error("Path index must be non-negative");
|
|
123
|
+
if (!options.order.every((detector) => Object.keys(detectors).includes(detector))) throw new Error("Invalid detector type in order array");
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Cache detected language
|
|
127
|
+
*/
|
|
128
|
+
function cacheLanguage(c, language, options) {
|
|
129
|
+
if (!Array.isArray(options.caches) || !options.caches.includes("cookie")) return;
|
|
130
|
+
try {
|
|
131
|
+
setCookie(c, options.lookupCookie, language, options.cookieOptions);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (options.debug) console.error("Failed to cache language:", error);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Detect language from request
|
|
138
|
+
*/
|
|
139
|
+
const detectLanguage = (c, options) => {
|
|
140
|
+
let detectedLang;
|
|
141
|
+
for (const detectorName of options.order) {
|
|
142
|
+
const detector = detectors[detectorName];
|
|
143
|
+
try {
|
|
144
|
+
detectedLang = detector(c, options);
|
|
145
|
+
if (detectedLang) {
|
|
146
|
+
if (options.debug) console.log(`Language detected from ${detectorName}: ${detectedLang}`);
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
} catch (error) {
|
|
150
|
+
if (options.debug) console.error(`Error in ${detectorName} detector:`, error);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const finalLang = detectedLang || options.fallbackLanguage;
|
|
155
|
+
if (detectedLang && options.caches) cacheLanguage(c, finalLang, options);
|
|
156
|
+
return finalLang;
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Language detector middleware factory
|
|
160
|
+
* @param userOptions Configuration options for the language detector
|
|
161
|
+
* @returns Acryl middleware function
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```ts
|
|
165
|
+
* type Variables = LanguageVariables
|
|
166
|
+
* const app = new Acryl<{ Variables: Variables }>()
|
|
167
|
+
*
|
|
168
|
+
* app.use(
|
|
169
|
+
* languageDetector({
|
|
170
|
+
* supportedLanguages: ['en', 'ja'], // Must include fallback
|
|
171
|
+
* fallbackLanguage: 'en', // Required
|
|
172
|
+
* })
|
|
173
|
+
* )
|
|
174
|
+
*
|
|
175
|
+
* app.get('/', (c) => {
|
|
176
|
+
* const lang = c.get('language')
|
|
177
|
+
* return c.text(`Current language: ${lang}`)
|
|
178
|
+
* })
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
const languageDetector = (userOptions) => {
|
|
182
|
+
const options = {
|
|
183
|
+
...DEFAULT_OPTIONS,
|
|
184
|
+
...userOptions,
|
|
185
|
+
cookieOptions: {
|
|
186
|
+
...DEFAULT_OPTIONS.cookieOptions,
|
|
187
|
+
...userOptions.cookieOptions
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
validateOptions(options);
|
|
191
|
+
return async function languageDetector(ctx, next) {
|
|
192
|
+
const lang = detectLanguage(ctx, options);
|
|
193
|
+
ctx.set("language", lang);
|
|
194
|
+
await next();
|
|
195
|
+
};
|
|
196
|
+
};
|
|
197
|
+
//#endregion
|
|
198
|
+
export { languageDetector as a, detectFromQuery as i, detectFromHeader as n, detectFromPath as r, detectFromCookie as t };
|