@thebes/cadmus 0.2.1
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/LICENSE +21 -0
- package/README.md +150 -0
- package/dist/astro/index.cjs +149 -0
- package/dist/astro/index.cjs.map +1 -0
- package/dist/astro/index.d.cts +101 -0
- package/dist/astro/index.d.cts.map +1 -0
- package/dist/astro/index.d.ts +101 -0
- package/dist/astro/index.d.ts.map +1 -0
- package/dist/astro/index.js +146 -0
- package/dist/astro/index.js.map +1 -0
- package/dist/auth/index.cjs +59 -0
- package/dist/auth/index.cjs.map +1 -0
- package/dist/auth/index.d.cts +14 -0
- package/dist/auth/index.d.cts.map +1 -0
- package/dist/auth/index.d.ts +14 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +54 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/cache/index.cjs +18 -0
- package/dist/cache/index.cjs.map +1 -0
- package/dist/cache/index.d.cts +10 -0
- package/dist/cache/index.d.cts.map +1 -0
- package/dist/cache/index.d.ts +10 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +17 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cms/index.cjs +763 -0
- package/dist/cms/index.cjs.map +1 -0
- package/dist/cms/index.d.cts +2 -0
- package/dist/cms/index.d.ts +2 -0
- package/dist/cms/index.js +743 -0
- package/dist/cms/index.js.map +1 -0
- package/dist/db/index.cjs +10 -0
- package/dist/db/index.cjs.map +1 -0
- package/dist/db/index.d.cts +7 -0
- package/dist/db/index.d.cts.map +1 -0
- package/dist/db/index.d.ts +7 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +9 -0
- package/dist/db/index.js.map +1 -0
- package/dist/email/index.cjs +25 -0
- package/dist/email/index.cjs.map +1 -0
- package/dist/email/index.d.cts +12 -0
- package/dist/email/index.d.cts.map +1 -0
- package/dist/email/index.d.ts +12 -0
- package/dist/email/index.d.ts.map +1 -0
- package/dist/email/index.js +24 -0
- package/dist/email/index.js.map +1 -0
- package/dist/errors-CW6Lz0AQ.cjs +196 -0
- package/dist/errors-CW6Lz0AQ.cjs.map +1 -0
- package/dist/errors-mZIqZJO4.js +125 -0
- package/dist/errors-mZIqZJO4.js.map +1 -0
- package/dist/hono/index.cjs +132 -0
- package/dist/hono/index.cjs.map +1 -0
- package/dist/hono/index.d.cts +59 -0
- package/dist/hono/index.d.cts.map +1 -0
- package/dist/hono/index.d.ts +59 -0
- package/dist/hono/index.d.ts.map +1 -0
- package/dist/hono/index.js +130 -0
- package/dist/hono/index.js.map +1 -0
- package/dist/index-BUrCSGVb.d.cts +616 -0
- package/dist/index-BUrCSGVb.d.cts.map +1 -0
- package/dist/index-BUrCSGVb.d.ts +616 -0
- package/dist/index-BUrCSGVb.d.ts.map +1 -0
- package/dist/index.cjs +60 -0
- package/dist/index.d.cts +107 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +107 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/queues/index.cjs +31 -0
- package/dist/queues/index.cjs.map +1 -0
- package/dist/queues/index.d.cts +22 -0
- package/dist/queues/index.d.cts.map +1 -0
- package/dist/queues/index.d.ts +22 -0
- package/dist/queues/index.d.ts.map +1 -0
- package/dist/queues/index.js +29 -0
- package/dist/queues/index.js.map +1 -0
- package/dist/rate-limit/index.cjs +38 -0
- package/dist/rate-limit/index.cjs.map +1 -0
- package/dist/rate-limit/index.d.cts +14 -0
- package/dist/rate-limit/index.d.cts.map +1 -0
- package/dist/rate-limit/index.d.ts +14 -0
- package/dist/rate-limit/index.d.ts.map +1 -0
- package/dist/rate-limit/index.js +37 -0
- package/dist/rate-limit/index.js.map +1 -0
- package/dist/session/index.cjs +48 -0
- package/dist/session/index.cjs.map +1 -0
- package/dist/session/index.d.cts +14 -0
- package/dist/session/index.d.cts.map +1 -0
- package/dist/session/index.d.ts +14 -0
- package/dist/session/index.d.ts.map +1 -0
- package/dist/session/index.js +45 -0
- package/dist/session/index.js.map +1 -0
- package/dist/storage/index.cjs +29 -0
- package/dist/storage/index.cjs.map +1 -0
- package/dist/storage/index.d.cts +38 -0
- package/dist/storage/index.d.cts.map +1 -0
- package/dist/storage/index.d.ts +38 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +26 -0
- package/dist/storage/index.js.map +1 -0
- package/package.json +115 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/session/index.d.ts
|
|
2
|
+
/** Stores `value` under `key` in KV, JSON-serialized, with a TTL in seconds. */
|
|
3
|
+
declare function createSession<T>(kv: KVNamespace, key: string, value: T, ttlSeconds: number): Promise<void>;
|
|
4
|
+
/**
|
|
5
|
+
* Reads and JSON-parses the session at `key`. Retries on a miss (KV's
|
|
6
|
+
* eventual consistency can otherwise produce a false negative right after
|
|
7
|
+
* a write) before returning null.
|
|
8
|
+
*/
|
|
9
|
+
declare function getSession<T>(kv: KVNamespace, key: string): Promise<T | null>;
|
|
10
|
+
/** Deletes the session at `key`. */
|
|
11
|
+
declare function deleteSession(kv: KVNamespace, key: string): Promise<void>;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { createSession, deleteSession, getSession };
|
|
14
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/session/index.ts"],"mappings":";;iBAsBsB,aAAA,IACpB,EAAA,EAAI,WAAA,EACJ,GAAA,UACA,KAAA,EAAO,CAAA,EACP,UAAA,WACC,OAAA;;;;;;iBAamB,UAAA,IACpB,EAAA,EAAI,WAAA,EACJ,GAAA,WACC,OAAA,CAAQ,CAAA;;iBAeW,aAAA,CACpB,EAAA,EAAI,WAAA,EACJ,GAAA,WACC,OAAO"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/session/index.d.ts
|
|
2
|
+
/** Stores `value` under `key` in KV, JSON-serialized, with a TTL in seconds. */
|
|
3
|
+
declare function createSession<T>(kv: KVNamespace, key: string, value: T, ttlSeconds: number): Promise<void>;
|
|
4
|
+
/**
|
|
5
|
+
* Reads and JSON-parses the session at `key`. Retries on a miss (KV's
|
|
6
|
+
* eventual consistency can otherwise produce a false negative right after
|
|
7
|
+
* a write) before returning null.
|
|
8
|
+
*/
|
|
9
|
+
declare function getSession<T>(kv: KVNamespace, key: string): Promise<T | null>;
|
|
10
|
+
/** Deletes the session at `key`. */
|
|
11
|
+
declare function deleteSession(kv: KVNamespace, key: string): Promise<void>;
|
|
12
|
+
//#endregion
|
|
13
|
+
export { createSession, deleteSession, getSession };
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/session/index.ts"],"mappings":";;iBAsBsB,aAAA,IACpB,EAAA,EAAI,WAAA,EACJ,GAAA,UACA,KAAA,EAAO,CAAA,EACP,UAAA,WACC,OAAA;;;;;;iBAamB,UAAA,IACpB,EAAA,EAAI,WAAA,EACJ,GAAA,WACC,OAAA,CAAQ,CAAA;;iBAeW,aAAA,CACpB,EAAA,EAAI,WAAA,EACJ,GAAA,WACC,OAAO"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { d as CadmusSessionError } from "../errors-mZIqZJO4.js";
|
|
2
|
+
//#region src/session/index.ts
|
|
3
|
+
const RETRY_ATTEMPTS = 2;
|
|
4
|
+
const RETRY_DELAY_MS = 100;
|
|
5
|
+
function sleep(ms) {
|
|
6
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
|
+
}
|
|
8
|
+
/** Stores `value` under `key` in KV, JSON-serialized, with a TTL in seconds. */
|
|
9
|
+
async function createSession(kv, key, value, ttlSeconds) {
|
|
10
|
+
try {
|
|
11
|
+
await kv.put(key, JSON.stringify(value), { expirationTtl: ttlSeconds });
|
|
12
|
+
} catch (cause) {
|
|
13
|
+
throw new CadmusSessionError(`Failed to create session "${key}"`, cause);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Reads and JSON-parses the session at `key`. Retries on a miss (KV's
|
|
18
|
+
* eventual consistency can otherwise produce a false negative right after
|
|
19
|
+
* a write) before returning null.
|
|
20
|
+
*/
|
|
21
|
+
async function getSession(kv, key) {
|
|
22
|
+
for (let attempt = 0; attempt <= RETRY_ATTEMPTS; attempt++) {
|
|
23
|
+
let raw;
|
|
24
|
+
try {
|
|
25
|
+
raw = await kv.get(key);
|
|
26
|
+
} catch (cause) {
|
|
27
|
+
throw new CadmusSessionError(`Failed to read session "${key}"`, cause);
|
|
28
|
+
}
|
|
29
|
+
if (raw !== null) return JSON.parse(raw);
|
|
30
|
+
if (attempt < RETRY_ATTEMPTS) await sleep(RETRY_DELAY_MS);
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
/** Deletes the session at `key`. */
|
|
35
|
+
async function deleteSession(kv, key) {
|
|
36
|
+
try {
|
|
37
|
+
await kv.delete(key);
|
|
38
|
+
} catch (cause) {
|
|
39
|
+
throw new CadmusSessionError(`Failed to delete session "${key}"`, cause);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { createSession, deleteSession, getSession };
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/session/index.ts"],"sourcesContent":["// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n//\n// @thebes/cadmus/session\n//\n// Thin JSON-over-KV session store. Takes a raw KVNamespace and a caller-\n// chosen key — no \"session:\" prefix or namespace convention baked in,\n// since that's an app-level choice, not a framework one. KV is eventually\n// consistent, so getSession() retries on a miss (a write immediately\n// followed by a read on a different edge location can otherwise see a\n// false negative) before concluding the session genuinely doesn't exist.\n\nimport { CadmusSessionError } from \"../errors.js\";\n\nconst RETRY_ATTEMPTS = 2;\nconst RETRY_DELAY_MS = 100;\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/** Stores `value` under `key` in KV, JSON-serialized, with a TTL in seconds. */\nexport async function createSession<T>(\n kv: KVNamespace,\n key: string,\n value: T,\n ttlSeconds: number,\n): Promise<void> {\n try {\n await kv.put(key, JSON.stringify(value), { expirationTtl: ttlSeconds });\n } catch (cause) {\n throw new CadmusSessionError(`Failed to create session \"${key}\"`, cause);\n }\n}\n\n/**\n * Reads and JSON-parses the session at `key`. Retries on a miss (KV's\n * eventual consistency can otherwise produce a false negative right after\n * a write) before returning null.\n */\nexport async function getSession<T>(\n kv: KVNamespace,\n key: string,\n): Promise<T | null> {\n for (let attempt = 0; attempt <= RETRY_ATTEMPTS; attempt++) {\n let raw: string | null;\n try {\n raw = await kv.get(key);\n } catch (cause) {\n throw new CadmusSessionError(`Failed to read session \"${key}\"`, cause);\n }\n if (raw !== null) return JSON.parse(raw) as T;\n if (attempt < RETRY_ATTEMPTS) await sleep(RETRY_DELAY_MS);\n }\n return null;\n}\n\n/** Deletes the session at `key`. */\nexport async function deleteSession(\n kv: KVNamespace,\n key: string,\n): Promise<void> {\n try {\n await kv.delete(key);\n } catch (cause) {\n throw new CadmusSessionError(`Failed to delete session \"${key}\"`, cause);\n }\n}\n"],"mappings":";;AAcA,MAAM,iBAAiB;AACvB,MAAM,iBAAiB;AAEvB,SAAS,MAAM,IAA2B;CACxC,OAAO,IAAI,SAAS,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;;AAGA,eAAsB,cACpB,IACA,KACA,OACA,YACe;CACf,IAAI;EACF,MAAM,GAAG,IAAI,KAAK,KAAK,UAAU,KAAK,GAAG,EAAE,eAAe,WAAW,CAAC;CACxE,SAAS,OAAO;EACd,MAAM,IAAI,mBAAmB,6BAA6B,IAAI,IAAI,KAAK;CACzE;AACF;;;;;;AAOA,eAAsB,WACpB,IACA,KACmB;CACnB,KAAK,IAAI,UAAU,GAAG,WAAW,gBAAgB,WAAW;EAC1D,IAAI;EACJ,IAAI;GACF,MAAM,MAAM,GAAG,IAAI,GAAG;EACxB,SAAS,OAAO;GACd,MAAM,IAAI,mBAAmB,2BAA2B,IAAI,IAAI,KAAK;EACvE;EACA,IAAI,QAAQ,MAAM,OAAO,KAAK,MAAM,GAAG;EACvC,IAAI,UAAU,gBAAgB,MAAM,MAAM,cAAc;CAC1D;CACA,OAAO;AACT;;AAGA,eAAsB,cACpB,IACA,KACe;CACf,IAAI;EACF,MAAM,GAAG,OAAO,GAAG;CACrB,SAAS,OAAO;EACd,MAAM,IAAI,mBAAmB,6BAA6B,IAAI,IAAI,KAAK;CACzE;AACF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_errors = require("../errors-CW6Lz0AQ.cjs");
|
|
3
|
+
//#region src/storage/index.ts
|
|
4
|
+
/** MIME types `validateImageFile` accepts — never trust a client-sent
|
|
5
|
+
* MIME type beyond this whitelist check; it's still just `file.type`,
|
|
6
|
+
* not a real content sniff, but rules out the obviously-wrong cases. */
|
|
7
|
+
const IMAGE_MIME_WHITELIST = [
|
|
8
|
+
"image/jpeg",
|
|
9
|
+
"image/png",
|
|
10
|
+
"image/webp",
|
|
11
|
+
"image/gif"
|
|
12
|
+
];
|
|
13
|
+
const MAX_IMAGE_UPLOAD_BYTES = 5 * 1024 * 1024;
|
|
14
|
+
/**
|
|
15
|
+
* Validates a file against the image MIME whitelist and 5MB size cap
|
|
16
|
+
* before it ever reaches `ImageService.upload()`/R2. Throws
|
|
17
|
+
* `CadmusStorageError` — callers map that to the right HTTP status
|
|
18
|
+
* (400 for an invalid type, 413 for oversize).
|
|
19
|
+
*/
|
|
20
|
+
function validateImageFile(file) {
|
|
21
|
+
if (!IMAGE_MIME_WHITELIST.includes(file.type)) throw new require_errors.CadmusStorageError(`Unsupported file type: ${file.type || "unknown"}`);
|
|
22
|
+
if (file.size > 5242880) throw new require_errors.CadmusStorageError(`File exceeds the ${MAX_IMAGE_UPLOAD_BYTES / (1024 * 1024)}MB limit`);
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
exports.IMAGE_MIME_WHITELIST = IMAGE_MIME_WHITELIST;
|
|
26
|
+
exports.MAX_IMAGE_UPLOAD_BYTES = MAX_IMAGE_UPLOAD_BYTES;
|
|
27
|
+
exports.validateImageFile = validateImageFile;
|
|
28
|
+
|
|
29
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["CadmusStorageError"],"sources":["../../src/storage/index.ts"],"sourcesContent":["// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n//\n// @thebes/cadmus/storage\n//\n// Defines the ImageService contract only — R2 upload/serve and any\n// alternate implementation (e.g. a Cloudflare Images extension) live\n// outside this primitive. Cadmus ships the interface so apps can swap\n// implementations without touching any component, renderer, or block\n// data; it has no opinion on which implementation is active.\nimport { CadmusStorageError } from \"../errors.js\";\n\n/** A rendered `<img>`-ready description of a stored image. */\nexport interface RenderedImage {\n src: string;\n srcset?: string;\n sizes?: string;\n}\n\n/**\n * Resolved once per app and imported everywhere images are read or\n * written. Never construct storage URLs or `cdn-cgi/image/...` paths\n * inline — always go through an `ImageService`.\n */\nexport interface ImageService {\n upload: (file: File) => Promise<{ url: string }>;\n render: (image: {\n url: string;\n width?: number;\n height?: number;\n alt: string;\n }) => RenderedImage;\n}\n\n/** MIME types `validateImageFile` accepts — never trust a client-sent\n * MIME type beyond this whitelist check; it's still just `file.type`,\n * not a real content sniff, but rules out the obviously-wrong cases. */\nexport const IMAGE_MIME_WHITELIST = [\n \"image/jpeg\",\n \"image/png\",\n \"image/webp\",\n \"image/gif\",\n] as const;\n\nexport const MAX_IMAGE_UPLOAD_BYTES = 5 * 1024 * 1024; // 5MB\n\n/**\n * Validates a file against the image MIME whitelist and 5MB size cap\n * before it ever reaches `ImageService.upload()`/R2. Throws\n * `CadmusStorageError` — callers map that to the right HTTP status\n * (400 for an invalid type, 413 for oversize).\n */\nexport function validateImageFile(file: File): void {\n if (\n !IMAGE_MIME_WHITELIST.includes(\n file.type as (typeof IMAGE_MIME_WHITELIST)[number],\n )\n ) {\n throw new CadmusStorageError(\n `Unsupported file type: ${file.type || \"unknown\"}`,\n );\n }\n if (file.size > MAX_IMAGE_UPLOAD_BYTES) {\n throw new CadmusStorageError(\n `File exceeds the ${MAX_IMAGE_UPLOAD_BYTES / (1024 * 1024)}MB limit`,\n );\n }\n}\n"],"mappings":";;;;;;AAqCA,MAAa,uBAAuB;CAClC;CACA;CACA;CACA;AACF;AAEA,MAAa,yBAAyB,IAAI,OAAO;;;;;;;AAQjD,SAAgB,kBAAkB,MAAkB;CAClD,IACE,CAAC,qBAAqB,SACpB,KAAK,IACP,GAEA,MAAM,IAAIA,eAAAA,mBACR,0BAA0B,KAAK,QAAQ,WACzC;CAEF,IAAI,KAAK,OAAA,SACP,MAAM,IAAIA,eAAAA,mBACR,oBAAoB,0BAA0B,OAAO,MAAM,SAC7D;AAEJ"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//#region src/storage/index.d.ts
|
|
2
|
+
/** A rendered `<img>`-ready description of a stored image. */
|
|
3
|
+
interface RenderedImage {
|
|
4
|
+
src: string;
|
|
5
|
+
srcset?: string;
|
|
6
|
+
sizes?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Resolved once per app and imported everywhere images are read or
|
|
10
|
+
* written. Never construct storage URLs or `cdn-cgi/image/...` paths
|
|
11
|
+
* inline — always go through an `ImageService`.
|
|
12
|
+
*/
|
|
13
|
+
interface ImageService {
|
|
14
|
+
upload: (file: File) => Promise<{
|
|
15
|
+
url: string;
|
|
16
|
+
}>;
|
|
17
|
+
render: (image: {
|
|
18
|
+
url: string;
|
|
19
|
+
width?: number;
|
|
20
|
+
height?: number;
|
|
21
|
+
alt: string;
|
|
22
|
+
}) => RenderedImage;
|
|
23
|
+
}
|
|
24
|
+
/** MIME types `validateImageFile` accepts — never trust a client-sent
|
|
25
|
+
* MIME type beyond this whitelist check; it's still just `file.type`,
|
|
26
|
+
* not a real content sniff, but rules out the obviously-wrong cases. */
|
|
27
|
+
declare const IMAGE_MIME_WHITELIST: readonly ["image/jpeg", "image/png", "image/webp", "image/gif"];
|
|
28
|
+
declare const MAX_IMAGE_UPLOAD_BYTES: number;
|
|
29
|
+
/**
|
|
30
|
+
* Validates a file against the image MIME whitelist and 5MB size cap
|
|
31
|
+
* before it ever reaches `ImageService.upload()`/R2. Throws
|
|
32
|
+
* `CadmusStorageError` — callers map that to the right HTTP status
|
|
33
|
+
* (400 for an invalid type, 413 for oversize).
|
|
34
|
+
*/
|
|
35
|
+
declare function validateImageFile(file: File): void;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { IMAGE_MIME_WHITELIST, ImageService, MAX_IMAGE_UPLOAD_BYTES, RenderedImage, validateImageFile };
|
|
38
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/storage/index.ts"],"mappings":";;UAaiB,aAAA;EACf,GAAA;EACA,MAAA;EACA,KAAA;AAAA;;;;AAAK;AAQP;UAAiB,YAAA;EACf,MAAA,GAAS,IAAA,EAAM,IAAA,KAAS,OAAA;IAAU,GAAA;EAAA;EAClC,MAAA,GAAS,KAAA;IACP,GAAA;IACA,KAAA;IACA,MAAA;IACA,GAAA;EAAA,MACI,aAAA;AAAA;;;;cAMK,oBAAA;AAAA,cAOA,sBAAA;;;;;AAbQ;AAMrB;iBAegB,iBAAA,CAAkB,IAAU,EAAJ,IAAI"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//#region src/storage/index.d.ts
|
|
2
|
+
/** A rendered `<img>`-ready description of a stored image. */
|
|
3
|
+
interface RenderedImage {
|
|
4
|
+
src: string;
|
|
5
|
+
srcset?: string;
|
|
6
|
+
sizes?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Resolved once per app and imported everywhere images are read or
|
|
10
|
+
* written. Never construct storage URLs or `cdn-cgi/image/...` paths
|
|
11
|
+
* inline — always go through an `ImageService`.
|
|
12
|
+
*/
|
|
13
|
+
interface ImageService {
|
|
14
|
+
upload: (file: File) => Promise<{
|
|
15
|
+
url: string;
|
|
16
|
+
}>;
|
|
17
|
+
render: (image: {
|
|
18
|
+
url: string;
|
|
19
|
+
width?: number;
|
|
20
|
+
height?: number;
|
|
21
|
+
alt: string;
|
|
22
|
+
}) => RenderedImage;
|
|
23
|
+
}
|
|
24
|
+
/** MIME types `validateImageFile` accepts — never trust a client-sent
|
|
25
|
+
* MIME type beyond this whitelist check; it's still just `file.type`,
|
|
26
|
+
* not a real content sniff, but rules out the obviously-wrong cases. */
|
|
27
|
+
declare const IMAGE_MIME_WHITELIST: readonly ["image/jpeg", "image/png", "image/webp", "image/gif"];
|
|
28
|
+
declare const MAX_IMAGE_UPLOAD_BYTES: number;
|
|
29
|
+
/**
|
|
30
|
+
* Validates a file against the image MIME whitelist and 5MB size cap
|
|
31
|
+
* before it ever reaches `ImageService.upload()`/R2. Throws
|
|
32
|
+
* `CadmusStorageError` — callers map that to the right HTTP status
|
|
33
|
+
* (400 for an invalid type, 413 for oversize).
|
|
34
|
+
*/
|
|
35
|
+
declare function validateImageFile(file: File): void;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { IMAGE_MIME_WHITELIST, ImageService, MAX_IMAGE_UPLOAD_BYTES, RenderedImage, validateImageFile };
|
|
38
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/storage/index.ts"],"mappings":";;UAaiB,aAAA;EACf,GAAA;EACA,MAAA;EACA,KAAA;AAAA;;;;AAAK;AAQP;UAAiB,YAAA;EACf,MAAA,GAAS,IAAA,EAAM,IAAA,KAAS,OAAA;IAAU,GAAA;EAAA;EAClC,MAAA,GAAS,KAAA;IACP,GAAA;IACA,KAAA;IACA,MAAA;IACA,GAAA;EAAA,MACI,aAAA;AAAA;;;;cAMK,oBAAA;AAAA,cAOA,sBAAA;;;;;AAbQ;AAMrB;iBAegB,iBAAA,CAAkB,IAAU,EAAJ,IAAI"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { f as CadmusStorageError } from "../errors-mZIqZJO4.js";
|
|
2
|
+
//#region src/storage/index.ts
|
|
3
|
+
/** MIME types `validateImageFile` accepts — never trust a client-sent
|
|
4
|
+
* MIME type beyond this whitelist check; it's still just `file.type`,
|
|
5
|
+
* not a real content sniff, but rules out the obviously-wrong cases. */
|
|
6
|
+
const IMAGE_MIME_WHITELIST = [
|
|
7
|
+
"image/jpeg",
|
|
8
|
+
"image/png",
|
|
9
|
+
"image/webp",
|
|
10
|
+
"image/gif"
|
|
11
|
+
];
|
|
12
|
+
const MAX_IMAGE_UPLOAD_BYTES = 5 * 1024 * 1024;
|
|
13
|
+
/**
|
|
14
|
+
* Validates a file against the image MIME whitelist and 5MB size cap
|
|
15
|
+
* before it ever reaches `ImageService.upload()`/R2. Throws
|
|
16
|
+
* `CadmusStorageError` — callers map that to the right HTTP status
|
|
17
|
+
* (400 for an invalid type, 413 for oversize).
|
|
18
|
+
*/
|
|
19
|
+
function validateImageFile(file) {
|
|
20
|
+
if (!IMAGE_MIME_WHITELIST.includes(file.type)) throw new CadmusStorageError(`Unsupported file type: ${file.type || "unknown"}`);
|
|
21
|
+
if (file.size > 5242880) throw new CadmusStorageError(`File exceeds the ${MAX_IMAGE_UPLOAD_BYTES / (1024 * 1024)}MB limit`);
|
|
22
|
+
}
|
|
23
|
+
//#endregion
|
|
24
|
+
export { IMAGE_MIME_WHITELIST, MAX_IMAGE_UPLOAD_BYTES, validateImageFile };
|
|
25
|
+
|
|
26
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/storage/index.ts"],"sourcesContent":["// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n//\n// @thebes/cadmus/storage\n//\n// Defines the ImageService contract only — R2 upload/serve and any\n// alternate implementation (e.g. a Cloudflare Images extension) live\n// outside this primitive. Cadmus ships the interface so apps can swap\n// implementations without touching any component, renderer, or block\n// data; it has no opinion on which implementation is active.\nimport { CadmusStorageError } from \"../errors.js\";\n\n/** A rendered `<img>`-ready description of a stored image. */\nexport interface RenderedImage {\n src: string;\n srcset?: string;\n sizes?: string;\n}\n\n/**\n * Resolved once per app and imported everywhere images are read or\n * written. Never construct storage URLs or `cdn-cgi/image/...` paths\n * inline — always go through an `ImageService`.\n */\nexport interface ImageService {\n upload: (file: File) => Promise<{ url: string }>;\n render: (image: {\n url: string;\n width?: number;\n height?: number;\n alt: string;\n }) => RenderedImage;\n}\n\n/** MIME types `validateImageFile` accepts — never trust a client-sent\n * MIME type beyond this whitelist check; it's still just `file.type`,\n * not a real content sniff, but rules out the obviously-wrong cases. */\nexport const IMAGE_MIME_WHITELIST = [\n \"image/jpeg\",\n \"image/png\",\n \"image/webp\",\n \"image/gif\",\n] as const;\n\nexport const MAX_IMAGE_UPLOAD_BYTES = 5 * 1024 * 1024; // 5MB\n\n/**\n * Validates a file against the image MIME whitelist and 5MB size cap\n * before it ever reaches `ImageService.upload()`/R2. Throws\n * `CadmusStorageError` — callers map that to the right HTTP status\n * (400 for an invalid type, 413 for oversize).\n */\nexport function validateImageFile(file: File): void {\n if (\n !IMAGE_MIME_WHITELIST.includes(\n file.type as (typeof IMAGE_MIME_WHITELIST)[number],\n )\n ) {\n throw new CadmusStorageError(\n `Unsupported file type: ${file.type || \"unknown\"}`,\n );\n }\n if (file.size > MAX_IMAGE_UPLOAD_BYTES) {\n throw new CadmusStorageError(\n `File exceeds the ${MAX_IMAGE_UPLOAD_BYTES / (1024 * 1024)}MB limit`,\n );\n }\n}\n"],"mappings":";;;;;AAqCA,MAAa,uBAAuB;CAClC;CACA;CACA;CACA;AACF;AAEA,MAAa,yBAAyB,IAAI,OAAO;;;;;;;AAQjD,SAAgB,kBAAkB,MAAkB;CAClD,IACE,CAAC,qBAAqB,SACpB,KAAK,IACP,GAEA,MAAM,IAAI,mBACR,0BAA0B,KAAK,QAAQ,WACzC;CAEF,IAAI,KAAK,OAAA,SACP,MAAM,IAAI,mBACR,oBAAoB,0BAA0B,OAAO,MAAM,SAC7D;AAEJ"}
|
package/package.json
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@thebes/cadmus",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "V8-first, Cloudflare-native full-stack framework primitives",
|
|
5
|
+
"author": "BowenLabs <hello@bowenlabs.io>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/bowenlabs/project-thebes",
|
|
10
|
+
"directory": "packages/cadmus"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/bowenlabs/project-thebes/tree/main/packages/cadmus#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/bowenlabs/project-thebes/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"cloudflare",
|
|
18
|
+
"cloudflare-workers",
|
|
19
|
+
"v8",
|
|
20
|
+
"framework",
|
|
21
|
+
"d1",
|
|
22
|
+
"kv",
|
|
23
|
+
"r2",
|
|
24
|
+
"hono",
|
|
25
|
+
"astro",
|
|
26
|
+
"tanstack"
|
|
27
|
+
],
|
|
28
|
+
"type": "module",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"default": "./dist/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./auth": {
|
|
35
|
+
"types": "./dist/auth/index.d.ts",
|
|
36
|
+
"default": "./dist/auth/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./db": {
|
|
39
|
+
"types": "./dist/db/index.d.ts",
|
|
40
|
+
"default": "./dist/db/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./storage": {
|
|
43
|
+
"types": "./dist/storage/index.d.ts",
|
|
44
|
+
"default": "./dist/storage/index.js"
|
|
45
|
+
},
|
|
46
|
+
"./cache": {
|
|
47
|
+
"types": "./dist/cache/index.d.ts",
|
|
48
|
+
"default": "./dist/cache/index.js"
|
|
49
|
+
},
|
|
50
|
+
"./email": {
|
|
51
|
+
"types": "./dist/email/index.d.ts",
|
|
52
|
+
"default": "./dist/email/index.js"
|
|
53
|
+
},
|
|
54
|
+
"./rate-limit": {
|
|
55
|
+
"types": "./dist/rate-limit/index.d.ts",
|
|
56
|
+
"default": "./dist/rate-limit/index.js"
|
|
57
|
+
},
|
|
58
|
+
"./session": {
|
|
59
|
+
"types": "./dist/session/index.d.ts",
|
|
60
|
+
"default": "./dist/session/index.js"
|
|
61
|
+
},
|
|
62
|
+
"./queues": {
|
|
63
|
+
"types": "./dist/queues/index.d.ts",
|
|
64
|
+
"default": "./dist/queues/index.js"
|
|
65
|
+
},
|
|
66
|
+
"./hono": {
|
|
67
|
+
"types": "./dist/hono/index.d.ts",
|
|
68
|
+
"default": "./dist/hono/index.js"
|
|
69
|
+
},
|
|
70
|
+
"./cms": {
|
|
71
|
+
"types": "./dist/cms/index.d.ts",
|
|
72
|
+
"default": "./dist/cms/index.js"
|
|
73
|
+
},
|
|
74
|
+
"./astro": {
|
|
75
|
+
"types": "./dist/astro/index.d.ts",
|
|
76
|
+
"default": "./dist/astro/index.js"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"peerDependencies": {
|
|
80
|
+
"astro": ">=5.0.0",
|
|
81
|
+
"hono": ">=4.0.0"
|
|
82
|
+
},
|
|
83
|
+
"peerDependenciesMeta": {
|
|
84
|
+
"astro": {
|
|
85
|
+
"optional": true
|
|
86
|
+
},
|
|
87
|
+
"hono": {
|
|
88
|
+
"optional": true
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"devDependencies": {
|
|
92
|
+
"@cloudflare/vitest-pool-workers": "latest",
|
|
93
|
+
"@cloudflare/workers-types": "latest",
|
|
94
|
+
"astro": "latest",
|
|
95
|
+
"hono": "latest",
|
|
96
|
+
"typescript": "latest",
|
|
97
|
+
"vite-plus": "latest",
|
|
98
|
+
"vitest": "latest"
|
|
99
|
+
},
|
|
100
|
+
"files": [
|
|
101
|
+
"dist",
|
|
102
|
+
"README.md",
|
|
103
|
+
"LICENSE"
|
|
104
|
+
],
|
|
105
|
+
"dependencies": {
|
|
106
|
+
"drizzle-orm": "^0.45.2",
|
|
107
|
+
"mimetext": "^3.0.28"
|
|
108
|
+
},
|
|
109
|
+
"scripts": {
|
|
110
|
+
"build": "vp pack",
|
|
111
|
+
"dev": "vp pack --watch",
|
|
112
|
+
"test": "vitest run",
|
|
113
|
+
"test:watch": "vitest"
|
|
114
|
+
}
|
|
115
|
+
}
|