@krizaka/storage 0.0.0-stage → 0.2.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.
@@ -0,0 +1,44 @@
1
+ // src/testkit.ts
2
+ import { expect, it } from "vitest";
3
+ var bytes = (...n) => new Uint8Array(n);
4
+ function objectStoreContract(name, make) {
5
+ it(`${name}: stores a file and reads it back byte for byte`, async () => {
6
+ const store = await make();
7
+ await store.put("site/home/a.b1.png", bytes(1, 2, 3, 255), { contentType: "image/png" });
8
+ const got = await store.get("site/home/a.b1.png");
9
+ expect([...got.bytes]).toEqual([1, 2, 3, 255]);
10
+ expect(got.contentType).toBe("image/png");
11
+ expect(await store.head("site/home/a.b1.png")).toEqual({ key: "site/home/a.b1.png", size: 4 });
12
+ });
13
+ it(`${name}: answers null for a missing file, and deleting it twice is fine`, async () => {
14
+ const store = await make();
15
+ expect(await store.get("missing/file.txt")).toBeNull();
16
+ expect(await store.head("missing/file.txt")).toBeNull();
17
+ await store.put("x/y.txt", bytes(1), { contentType: "text/plain" });
18
+ await store.delete("x/y.txt");
19
+ await store.delete("x/y.txt");
20
+ expect(await store.get("x/y.txt")).toBeNull();
21
+ });
22
+ it(`${name}: overwrites a key`, async () => {
23
+ const store = await make();
24
+ await store.put("k/f.bin", bytes(1), { contentType: "application/octet-stream" });
25
+ await store.put("k/f.bin", bytes(9, 9), { contentType: "application/octet-stream" });
26
+ expect([...(await store.get("k/f.bin")).bytes]).toEqual([9, 9]);
27
+ });
28
+ it(`${name}: lists the files under a prefix, directories flattened, in key order`, async () => {
29
+ const store = await make();
30
+ for (const key of ["a/one.txt", "a/b/two.txt", "a/b/c/three.txt", "other/four.txt", "a-b/five.txt"]) await store.put(key, bytes(7), { contentType: "text/plain" });
31
+ expect((await store.list("a/")).map((f) => f.key)).toEqual(["a/b/c/three.txt", "a/b/two.txt", "a/one.txt"]);
32
+ expect((await store.list("a/b/")).map((f) => f.key)).toEqual(["a/b/c/three.txt", "a/b/two.txt"]);
33
+ expect(await store.list("nothing/")).toEqual([]);
34
+ expect((await store.list("a/o")).map((f) => f.key)).toEqual(["a/one.txt"]);
35
+ });
36
+ it(`${name}: refuses a key that climbs out, is empty or has a bad segment`, async () => {
37
+ const store = await make();
38
+ for (const key of ["../x.txt", "a/../b.txt", "/abs.txt", "a//b.txt", "a/b/", "", "a\\b.txt"]) await expect(store.put(key, bytes(1), { contentType: "text/plain" })).rejects.toMatchObject({ code: "STORAGE_BAD_KEY" });
39
+ });
40
+ }
41
+ export {
42
+ objectStoreContract
43
+ };
44
+ //# sourceMappingURL=testkit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/testkit.ts"],"sourcesContent":["import { expect, it } from \"vitest\";\n\nimport type { ObjectStore } from \"./port\";\n\nconst bytes = (...n: number[]) => new Uint8Array(n);\n\n/** What every `ObjectStore` adapter must do: the same behaviour behind the same port. `make` gives an empty store. */\nexport function objectStoreContract(name: string, make: () => ObjectStore | Promise<ObjectStore>) {\n it(`${name}: stores a file and reads it back byte for byte`, async () => {\n const store = await make();\n await store.put(\"site/home/a.b1.png\", bytes(1, 2, 3, 255), { contentType: \"image/png\" });\n const got = await store.get(\"site/home/a.b1.png\");\n expect([...got!.bytes]).toEqual([1, 2, 3, 255]);\n expect(got!.contentType).toBe(\"image/png\");\n expect(await store.head(\"site/home/a.b1.png\")).toEqual({ key: \"site/home/a.b1.png\", size: 4 });\n });\n\n it(`${name}: answers null for a missing file, and deleting it twice is fine`, async () => {\n const store = await make();\n expect(await store.get(\"missing/file.txt\")).toBeNull();\n expect(await store.head(\"missing/file.txt\")).toBeNull();\n await store.put(\"x/y.txt\", bytes(1), { contentType: \"text/plain\" });\n await store.delete(\"x/y.txt\");\n await store.delete(\"x/y.txt\");\n expect(await store.get(\"x/y.txt\")).toBeNull();\n });\n\n it(`${name}: overwrites a key`, async () => {\n const store = await make();\n await store.put(\"k/f.bin\", bytes(1), { contentType: \"application/octet-stream\" });\n await store.put(\"k/f.bin\", bytes(9, 9), { contentType: \"application/octet-stream\" });\n expect([...(await store.get(\"k/f.bin\"))!.bytes]).toEqual([9, 9]);\n });\n\n it(`${name}: lists the files under a prefix, directories flattened, in key order`, async () => {\n const store = await make();\n for (const key of [\"a/one.txt\", \"a/b/two.txt\", \"a/b/c/three.txt\", \"other/four.txt\", \"a-b/five.txt\"]) await store.put(key, bytes(7), { contentType: \"text/plain\" });\n expect((await store.list(\"a/\")).map((f) => f.key)).toEqual([\"a/b/c/three.txt\", \"a/b/two.txt\", \"a/one.txt\"]);\n expect((await store.list(\"a/b/\")).map((f) => f.key)).toEqual([\"a/b/c/three.txt\", \"a/b/two.txt\"]);\n expect((await store.list(\"nothing/\"))).toEqual([]);\n expect((await store.list(\"a/o\")).map((f) => f.key)).toEqual([\"a/one.txt\"]);\n });\n\n it(`${name}: refuses a key that climbs out, is empty or has a bad segment`, async () => {\n const store = await make();\n for (const key of [\"../x.txt\", \"a/../b.txt\", \"/abs.txt\", \"a//b.txt\", \"a/b/\", \"\", \"a\\\\b.txt\"]) await expect(store.put(key, bytes(1), { contentType: \"text/plain\" })).rejects.toMatchObject({ code: \"STORAGE_BAD_KEY\" });\n });\n}\n"],"mappings":";AAAA,SAAS,QAAQ,UAAU;AAI3B,IAAM,QAAQ,IAAI,MAAgB,IAAI,WAAW,CAAC;AAG3C,SAAS,oBAAoB,MAAc,MAAgD;AAChG,KAAG,GAAG,IAAI,mDAAmD,YAAY;AACvE,UAAM,QAAQ,MAAM,KAAK;AACzB,UAAM,MAAM,IAAI,sBAAsB,MAAM,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE,aAAa,YAAY,CAAC;AACvF,UAAM,MAAM,MAAM,MAAM,IAAI,oBAAoB;AAChD,WAAO,CAAC,GAAG,IAAK,KAAK,CAAC,EAAE,QAAQ,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC;AAC9C,WAAO,IAAK,WAAW,EAAE,KAAK,WAAW;AACzC,WAAO,MAAM,MAAM,KAAK,oBAAoB,CAAC,EAAE,QAAQ,EAAE,KAAK,sBAAsB,MAAM,EAAE,CAAC;AAAA,EAC/F,CAAC;AAED,KAAG,GAAG,IAAI,oEAAoE,YAAY;AACxF,UAAM,QAAQ,MAAM,KAAK;AACzB,WAAO,MAAM,MAAM,IAAI,kBAAkB,CAAC,EAAE,SAAS;AACrD,WAAO,MAAM,MAAM,KAAK,kBAAkB,CAAC,EAAE,SAAS;AACtD,UAAM,MAAM,IAAI,WAAW,MAAM,CAAC,GAAG,EAAE,aAAa,aAAa,CAAC;AAClE,UAAM,MAAM,OAAO,SAAS;AAC5B,UAAM,MAAM,OAAO,SAAS;AAC5B,WAAO,MAAM,MAAM,IAAI,SAAS,CAAC,EAAE,SAAS;AAAA,EAC9C,CAAC;AAED,KAAG,GAAG,IAAI,sBAAsB,YAAY;AAC1C,UAAM,QAAQ,MAAM,KAAK;AACzB,UAAM,MAAM,IAAI,WAAW,MAAM,CAAC,GAAG,EAAE,aAAa,2BAA2B,CAAC;AAChF,UAAM,MAAM,IAAI,WAAW,MAAM,GAAG,CAAC,GAAG,EAAE,aAAa,2BAA2B,CAAC;AACnF,WAAO,CAAC,IAAI,MAAM,MAAM,IAAI,SAAS,GAAI,KAAK,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC;AAAA,EACjE,CAAC;AAED,KAAG,GAAG,IAAI,yEAAyE,YAAY;AAC7F,UAAM,QAAQ,MAAM,KAAK;AACzB,eAAW,OAAO,CAAC,aAAa,eAAe,mBAAmB,kBAAkB,cAAc,EAAG,OAAM,MAAM,IAAI,KAAK,MAAM,CAAC,GAAG,EAAE,aAAa,aAAa,CAAC;AACjK,YAAQ,MAAM,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,CAAC,mBAAmB,eAAe,WAAW,CAAC;AAC1G,YAAQ,MAAM,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,CAAC,mBAAmB,aAAa,CAAC;AAC/F,WAAQ,MAAM,MAAM,KAAK,UAAU,CAAE,EAAE,QAAQ,CAAC,CAAC;AACjD,YAAQ,MAAM,MAAM,KAAK,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,CAAC,WAAW,CAAC;AAAA,EAC3E,CAAC;AAED,KAAG,GAAG,IAAI,kEAAkE,YAAY;AACtF,UAAM,QAAQ,MAAM,KAAK;AACzB,eAAW,OAAO,CAAC,YAAY,cAAc,YAAY,YAAY,QAAQ,IAAI,UAAU,EAAG,OAAM,OAAO,MAAM,IAAI,KAAK,MAAM,CAAC,GAAG,EAAE,aAAa,aAAa,CAAC,CAAC,EAAE,QAAQ,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAAA,EACvN,CAAC;AACH;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,85 @@
1
1
  {
2
2
  "name": "@krizaka/storage",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "description": "Files behind one port for every Krizaka product: put with a content-hashed name, get, head, delete, list, signed and public URLs; adapters over an injected fetch for an HTTP object store and any S3-compatible bucket (Signature V4, no SDK), and a local folder; layout as data, bytes checked by magic number, limits passed in. The admin chooses the provider through settings.",
5
+ "keywords": [
6
+ "krizaka",
7
+ "storage",
8
+ "files",
9
+ "s3",
10
+ "http",
11
+ "cdn",
12
+ "upload"
13
+ ],
14
+ "homepage": "https://github.com/krizaka/krizaka-ts/tree/main/packages/storage",
15
+ "bugs": "https://github.com/krizaka/krizaka-ts/issues",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/krizaka/krizaka-ts.git",
19
+ "directory": "packages/storage"
20
+ },
21
+ "license": "Apache-2.0",
22
+ "author": "Krizaka (https://www.krizaka.com)",
23
+ "type": "module",
24
+ "main": "./dist/index.js",
25
+ "module": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js"
31
+ },
32
+ "./settings": {
33
+ "types": "./dist/settings.d.ts",
34
+ "import": "./dist/settings.js"
35
+ },
36
+ "./testkit": {
37
+ "types": "./dist/testkit.d.ts",
38
+ "import": "./dist/testkit.js"
39
+ },
40
+ "./package.json": "./package.json"
41
+ },
42
+ "files": [
43
+ "dist",
44
+ "README.md",
45
+ "LICENSE"
46
+ ],
47
+ "sideEffects": false,
48
+ "engines": {
49
+ "node": ">=22"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^22.10.0",
53
+ "@types/pg": "^8.23.1",
54
+ "eslint": "^9.39.0",
55
+ "pg": "^8.23.1",
56
+ "tsup": "^8.5.0",
57
+ "typescript": "^5.9.3",
58
+ "vitest": "^4.1.11",
59
+ "zod": "^4.6.7",
60
+ "@krizaka/config": "^0.8.1"
61
+ },
62
+ "dependencies": {
63
+ "@krizaka/db": "^0.2.0",
64
+ "@krizaka/settings": "^0.5.0"
65
+ },
66
+ "peerDependencies": {
67
+ "vitest": ">=4",
68
+ "zod": ">=3.23"
69
+ },
70
+ "peerDependenciesMeta": {
71
+ "vitest": {
72
+ "optional": true
73
+ },
74
+ "zod": {
75
+ "optional": true
76
+ }
77
+ },
78
+ "scripts": {
79
+ "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsup",
80
+ "lint": "eslint . --max-warnings 0 && tsc --noEmit",
81
+ "typecheck": "tsc --noEmit",
82
+ "test": "vitest run",
83
+ "check": "pnpm run lint && pnpm run test && pnpm run build"
84
+ }
6
85
  }