@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,125 @@
|
|
|
1
|
+
//#region src/errors.ts
|
|
2
|
+
/**
|
|
3
|
+
* Base class for all Cadmus errors.
|
|
4
|
+
* All primitives throw CadmusError or a typed subclass — never a raw Error.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* try {
|
|
8
|
+
* await createMagicLink({ kv, email, to })
|
|
9
|
+
* } catch (e) {
|
|
10
|
+
* if (e instanceof CadmusAuthError) {
|
|
11
|
+
* // auth-specific handling
|
|
12
|
+
* } else if (e instanceof CadmusError) {
|
|
13
|
+
* // any cadmus error — e.code tells you which primitive threw
|
|
14
|
+
* } else {
|
|
15
|
+
* throw e // re-throw unknown errors
|
|
16
|
+
* }
|
|
17
|
+
* }
|
|
18
|
+
*/
|
|
19
|
+
var CadmusError = class extends Error {
|
|
20
|
+
code;
|
|
21
|
+
cause;
|
|
22
|
+
constructor(message, code, cause) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.code = code;
|
|
25
|
+
this.cause = cause;
|
|
26
|
+
this.name = "CadmusError";
|
|
27
|
+
if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
/** Thrown by @thebes/cadmus/auth primitives */
|
|
31
|
+
var CadmusAuthError = class extends CadmusError {
|
|
32
|
+
constructor(message, cause) {
|
|
33
|
+
super(message, "AUTH_ERROR", cause);
|
|
34
|
+
this.name = "CadmusAuthError";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
/** Thrown by @thebes/cadmus/db primitives */
|
|
38
|
+
var CadmusDbError = class extends CadmusError {
|
|
39
|
+
constructor(message, cause) {
|
|
40
|
+
super(message, "DB_ERROR", cause);
|
|
41
|
+
this.name = "CadmusDbError";
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
/** Thrown by @thebes/cadmus/storage primitives */
|
|
45
|
+
var CadmusStorageError = class extends CadmusError {
|
|
46
|
+
constructor(message, cause) {
|
|
47
|
+
super(message, "STORAGE_ERROR", cause);
|
|
48
|
+
this.name = "CadmusStorageError";
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
/** Thrown by @thebes/cadmus/cache primitives */
|
|
52
|
+
var CadmusCacheError = class extends CadmusError {
|
|
53
|
+
constructor(message, cause) {
|
|
54
|
+
super(message, "CACHE_ERROR", cause);
|
|
55
|
+
this.name = "CadmusCacheError";
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
/** Thrown by @thebes/cadmus/email primitives */
|
|
59
|
+
var CadmusEmailError = class extends CadmusError {
|
|
60
|
+
constructor(message, cause) {
|
|
61
|
+
super(message, "EMAIL_ERROR", cause);
|
|
62
|
+
this.name = "CadmusEmailError";
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
/** Thrown by @thebes/cadmus/session primitives */
|
|
66
|
+
var CadmusSessionError = class extends CadmusError {
|
|
67
|
+
constructor(message, cause) {
|
|
68
|
+
super(message, "SESSION_ERROR", cause);
|
|
69
|
+
this.name = "CadmusSessionError";
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
/** Thrown by @thebes/cadmus/rate-limit primitives */
|
|
73
|
+
var CadmusRateLimitError = class extends CadmusError {
|
|
74
|
+
constructor(message, cause) {
|
|
75
|
+
super(message, "RATE_LIMIT_ERROR", cause);
|
|
76
|
+
this.name = "CadmusRateLimitError";
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
/** Thrown by @thebes/cadmus/queues primitives */
|
|
80
|
+
var CadmusQueueError = class extends CadmusError {
|
|
81
|
+
constructor(message, cause) {
|
|
82
|
+
super(message, "QUEUE_ERROR", cause);
|
|
83
|
+
this.name = "CadmusQueueError";
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
/** Thrown by @thebes/cadmus/cms primitives */
|
|
87
|
+
var CadmusCmsError = class extends CadmusError {
|
|
88
|
+
constructor(message, cause) {
|
|
89
|
+
super(message, "CMS_ERROR", cause);
|
|
90
|
+
this.name = "CadmusCmsError";
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Thrown by @thebes/cadmus/cms's createLocalApi when a collection's
|
|
95
|
+
* `access` function rejects an operation. A distinct subclass (rather than
|
|
96
|
+
* a plain CadmusCmsError) so consumers like mountCmsRoutes can map it to
|
|
97
|
+
* 403 by `instanceof`, not by matching on message text.
|
|
98
|
+
*/
|
|
99
|
+
var CadmusAccessDeniedError = class extends CadmusCmsError {
|
|
100
|
+
constructor(message, cause) {
|
|
101
|
+
super(message, cause);
|
|
102
|
+
this.name = "CadmusAccessDeniedError";
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Thrown by @thebes/cadmus/hono's `createCmsApiClient` when a request
|
|
107
|
+
* against a `mountCmsRoutes` surface returns a non-2xx response. Carries
|
|
108
|
+
* the HTTP status and parsed body so callers can branch on `status`
|
|
109
|
+
* (e.g. 403 → access denied, 404 → not found) instead of re-parsing
|
|
110
|
+
* `{ error: string }` response bodies by hand.
|
|
111
|
+
*/
|
|
112
|
+
var CadmusApiError = class extends CadmusError {
|
|
113
|
+
status;
|
|
114
|
+
body;
|
|
115
|
+
constructor(message, status, body) {
|
|
116
|
+
super(message, "API_ERROR");
|
|
117
|
+
this.status = status;
|
|
118
|
+
this.body = body;
|
|
119
|
+
this.name = "CadmusApiError";
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
//#endregion
|
|
123
|
+
export { CadmusCmsError as a, CadmusError as c, CadmusSessionError as d, CadmusStorageError as f, CadmusCacheError as i, CadmusQueueError as l, CadmusApiError as n, CadmusDbError as o, CadmusAuthError as r, CadmusEmailError as s, CadmusAccessDeniedError as t, CadmusRateLimitError as u };
|
|
124
|
+
|
|
125
|
+
//# sourceMappingURL=errors-mZIqZJO4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors-mZIqZJO4.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\n// `Error.captureStackTrace` is a real V8 engine feature available in\n// workerd's V8 isolates — it's just not part of any spec, so it isn't in\n// TypeScript's standard lib types without pulling in @types/node, which\n// Cadmus deliberately doesn't (V8-first, no Node assumptions).\ndeclare global {\n interface ErrorConstructor {\n // biome-ignore lint/complexity/noBannedTypes: matches the real V8 signature — this.constructor is typed as Function by TS itself\n captureStackTrace?(targetObject: object, constructorOpt?: Function): void;\n }\n}\n\n/**\n * Base class for all Cadmus errors.\n * All primitives throw CadmusError or a typed subclass — never a raw Error.\n *\n * @example\n * try {\n * await createMagicLink({ kv, email, to })\n * } catch (e) {\n * if (e instanceof CadmusAuthError) {\n * // auth-specific handling\n * } else if (e instanceof CadmusError) {\n * // any cadmus error — e.code tells you which primitive threw\n * } else {\n * throw e // re-throw unknown errors\n * }\n * }\n */\nexport class CadmusError extends Error {\n constructor(\n message: string,\n public readonly code: string,\n public readonly cause?: unknown,\n ) {\n super(message);\n this.name = \"CadmusError\";\n // Maintains proper stack trace in V8\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, this.constructor);\n }\n }\n}\n\n/** Thrown by @thebes/cadmus/auth primitives */\nexport class CadmusAuthError extends CadmusError {\n constructor(message: string, cause?: unknown) {\n super(message, \"AUTH_ERROR\", cause);\n this.name = \"CadmusAuthError\";\n }\n}\n\n/** Thrown by @thebes/cadmus/db primitives */\nexport class CadmusDbError extends CadmusError {\n constructor(message: string, cause?: unknown) {\n super(message, \"DB_ERROR\", cause);\n this.name = \"CadmusDbError\";\n }\n}\n\n/** Thrown by @thebes/cadmus/storage primitives */\nexport class CadmusStorageError extends CadmusError {\n constructor(message: string, cause?: unknown) {\n super(message, \"STORAGE_ERROR\", cause);\n this.name = \"CadmusStorageError\";\n }\n}\n\n/** Thrown by @thebes/cadmus/cache primitives */\nexport class CadmusCacheError extends CadmusError {\n constructor(message: string, cause?: unknown) {\n super(message, \"CACHE_ERROR\", cause);\n this.name = \"CadmusCacheError\";\n }\n}\n\n/** Thrown by @thebes/cadmus/email primitives */\nexport class CadmusEmailError extends CadmusError {\n constructor(message: string, cause?: unknown) {\n super(message, \"EMAIL_ERROR\", cause);\n this.name = \"CadmusEmailError\";\n }\n}\n\n/** Thrown by @thebes/cadmus/session primitives */\nexport class CadmusSessionError extends CadmusError {\n constructor(message: string, cause?: unknown) {\n super(message, \"SESSION_ERROR\", cause);\n this.name = \"CadmusSessionError\";\n }\n}\n\n/** Thrown by @thebes/cadmus/rate-limit primitives */\nexport class CadmusRateLimitError extends CadmusError {\n constructor(message: string, cause?: unknown) {\n super(message, \"RATE_LIMIT_ERROR\", cause);\n this.name = \"CadmusRateLimitError\";\n }\n}\n\n/** Thrown by @thebes/cadmus/queues primitives */\nexport class CadmusQueueError extends CadmusError {\n constructor(message: string, cause?: unknown) {\n super(message, \"QUEUE_ERROR\", cause);\n this.name = \"CadmusQueueError\";\n }\n}\n\n/** Thrown by @thebes/cadmus/cms primitives */\nexport class CadmusCmsError extends CadmusError {\n constructor(message: string, cause?: unknown) {\n super(message, \"CMS_ERROR\", cause);\n this.name = \"CadmusCmsError\";\n }\n}\n\n/**\n * Thrown by @thebes/cadmus/cms's createLocalApi when a collection's\n * `access` function rejects an operation. A distinct subclass (rather than\n * a plain CadmusCmsError) so consumers like mountCmsRoutes can map it to\n * 403 by `instanceof`, not by matching on message text.\n */\nexport class CadmusAccessDeniedError extends CadmusCmsError {\n constructor(message: string, cause?: unknown) {\n super(message, cause);\n this.name = \"CadmusAccessDeniedError\";\n }\n}\n\n/**\n * Thrown by @thebes/cadmus/hono's `createCmsApiClient` when a request\n * against a `mountCmsRoutes` surface returns a non-2xx response. Carries\n * the HTTP status and parsed body so callers can branch on `status`\n * (e.g. 403 → access denied, 404 → not found) instead of re-parsing\n * `{ error: string }` response bodies by hand.\n */\nexport class CadmusApiError extends CadmusError {\n constructor(\n message: string,\n public readonly status: number,\n public readonly body: unknown,\n ) {\n super(message, \"API_ERROR\");\n this.name = \"CadmusApiError\";\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA+BA,IAAa,cAAb,cAAiC,MAAM;CAGnB;CACA;CAHlB,YACE,SACA,MACA,OACA;EACA,MAAM,OAAO;EAHG,KAAA,OAAA;EACA,KAAA,QAAA;EAGhB,KAAK,OAAO;EAEZ,IAAI,MAAM,mBACR,MAAM,kBAAkB,MAAM,KAAK,WAAW;CAElD;AACF;;AAGA,IAAa,kBAAb,cAAqC,YAAY;CAC/C,YAAY,SAAiB,OAAiB;EAC5C,MAAM,SAAS,cAAc,KAAK;EAClC,KAAK,OAAO;CACd;AACF;;AAGA,IAAa,gBAAb,cAAmC,YAAY;CAC7C,YAAY,SAAiB,OAAiB;EAC5C,MAAM,SAAS,YAAY,KAAK;EAChC,KAAK,OAAO;CACd;AACF;;AAGA,IAAa,qBAAb,cAAwC,YAAY;CAClD,YAAY,SAAiB,OAAiB;EAC5C,MAAM,SAAS,iBAAiB,KAAK;EACrC,KAAK,OAAO;CACd;AACF;;AAGA,IAAa,mBAAb,cAAsC,YAAY;CAChD,YAAY,SAAiB,OAAiB;EAC5C,MAAM,SAAS,eAAe,KAAK;EACnC,KAAK,OAAO;CACd;AACF;;AAGA,IAAa,mBAAb,cAAsC,YAAY;CAChD,YAAY,SAAiB,OAAiB;EAC5C,MAAM,SAAS,eAAe,KAAK;EACnC,KAAK,OAAO;CACd;AACF;;AAGA,IAAa,qBAAb,cAAwC,YAAY;CAClD,YAAY,SAAiB,OAAiB;EAC5C,MAAM,SAAS,iBAAiB,KAAK;EACrC,KAAK,OAAO;CACd;AACF;;AAGA,IAAa,uBAAb,cAA0C,YAAY;CACpD,YAAY,SAAiB,OAAiB;EAC5C,MAAM,SAAS,oBAAoB,KAAK;EACxC,KAAK,OAAO;CACd;AACF;;AAGA,IAAa,mBAAb,cAAsC,YAAY;CAChD,YAAY,SAAiB,OAAiB;EAC5C,MAAM,SAAS,eAAe,KAAK;EACnC,KAAK,OAAO;CACd;AACF;;AAGA,IAAa,iBAAb,cAAoC,YAAY;CAC9C,YAAY,SAAiB,OAAiB;EAC5C,MAAM,SAAS,aAAa,KAAK;EACjC,KAAK,OAAO;CACd;AACF;;;;;;;AAQA,IAAa,0BAAb,cAA6C,eAAe;CAC1D,YAAY,SAAiB,OAAiB;EAC5C,MAAM,SAAS,KAAK;EACpB,KAAK,OAAO;CACd;AACF;;;;;;;;AASA,IAAa,iBAAb,cAAoC,YAAY;CAG5B;CACA;CAHlB,YACE,SACA,QACA,MACA;EACA,MAAM,SAAS,WAAW;EAHV,KAAA,SAAA;EACA,KAAA,OAAA;EAGhB,KAAK,OAAO;CACd;AACF"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_errors = require("../errors-CW6Lz0AQ.cjs");
|
|
3
|
+
let hono = require("hono");
|
|
4
|
+
//#region src/hono/client.ts
|
|
5
|
+
async function parseBody(response) {
|
|
6
|
+
const text = await response.text();
|
|
7
|
+
if (!text) return void 0;
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(text);
|
|
10
|
+
} catch {
|
|
11
|
+
return text;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function errorMessage(body, status) {
|
|
15
|
+
if (body && typeof body === "object" && "error" in body) {
|
|
16
|
+
const error = body.error;
|
|
17
|
+
if (typeof error === "string") return error;
|
|
18
|
+
}
|
|
19
|
+
return `Request failed with status ${status}`;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The client-side counterpart to `mountCmsRoutes` — talks to exactly the
|
|
23
|
+
* REST surface that function mounts (`GET/POST/PATCH/DELETE
|
|
24
|
+
* /api/:collection[...]`), via plain `fetch()`. No Node APIs, works from
|
|
25
|
+
* any environment (browser, Worker, Astro SSR).
|
|
26
|
+
*
|
|
27
|
+
* This is for callers *outside* the Worker process that's actually running
|
|
28
|
+
* the CMS — an Astro island, an external operator's own client, anything
|
|
29
|
+
* that can't call a `LocalApi` in-process. In-process callers (the same
|
|
30
|
+
* Worker's own server functions, Cadmea's own Panel) should keep calling
|
|
31
|
+
* the `LocalApi`/Hono RPC (`hc<AppType>`) directly — this client adds a
|
|
32
|
+
* network hop neither of those needs.
|
|
33
|
+
*/
|
|
34
|
+
function createCmsApiClient(baseUrl, options = {}) {
|
|
35
|
+
async function request(method, path, body) {
|
|
36
|
+
const headers = {};
|
|
37
|
+
const authHeader = await options.getAuthHeader?.();
|
|
38
|
+
if (authHeader) headers.Authorization = authHeader;
|
|
39
|
+
if (body !== void 0) headers["Content-Type"] = "application/json";
|
|
40
|
+
let response;
|
|
41
|
+
try {
|
|
42
|
+
response = await fetch(`${baseUrl}/api${path}`, {
|
|
43
|
+
method,
|
|
44
|
+
headers,
|
|
45
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
46
|
+
});
|
|
47
|
+
} catch (cause) {
|
|
48
|
+
throw new require_errors.CadmusApiError(`Request to "${baseUrl}/api${path}" failed`, 0, cause);
|
|
49
|
+
}
|
|
50
|
+
const parsed = await parseBody(response);
|
|
51
|
+
if (!response.ok) throw new require_errors.CadmusApiError(errorMessage(parsed, response.status), response.status, parsed);
|
|
52
|
+
return parsed;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
find(collection) {
|
|
56
|
+
return request("GET", `/${collection}`);
|
|
57
|
+
},
|
|
58
|
+
findByID(collection, id) {
|
|
59
|
+
return request("GET", `/${collection}/${id}`);
|
|
60
|
+
},
|
|
61
|
+
search(collection, query) {
|
|
62
|
+
return request("GET", `/${collection}/search?q=${encodeURIComponent(query)}`);
|
|
63
|
+
},
|
|
64
|
+
create(collection, data) {
|
|
65
|
+
return request("POST", `/${collection}`, data);
|
|
66
|
+
},
|
|
67
|
+
update(collection, id, data) {
|
|
68
|
+
return request("PATCH", `/${collection}/${id}`, data);
|
|
69
|
+
},
|
|
70
|
+
delete(collection, id) {
|
|
71
|
+
return request("DELETE", `/${collection}/${id}`);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/hono/cms.ts
|
|
77
|
+
function statusForError(error) {
|
|
78
|
+
if (error instanceof require_errors.CadmusAccessDeniedError) return 403;
|
|
79
|
+
if (error.message.includes("document found with id")) return 404;
|
|
80
|
+
if (error.message.includes("Unique constraint violated")) return 409;
|
|
81
|
+
return 400;
|
|
82
|
+
}
|
|
83
|
+
function getApi(collections, slug) {
|
|
84
|
+
const api = collections[slug];
|
|
85
|
+
if (!api) throw new require_errors.CadmusCmsError(`Unknown collection "${slug}"`);
|
|
86
|
+
return api;
|
|
87
|
+
}
|
|
88
|
+
function mountCmsRoutes(app, options) {
|
|
89
|
+
const router = new hono.Hono();
|
|
90
|
+
router.onError((error, c) => {
|
|
91
|
+
if (error instanceof require_errors.CadmusCmsError) return c.json({ error: error.message }, statusForError(error));
|
|
92
|
+
throw error;
|
|
93
|
+
});
|
|
94
|
+
router.get("/:collection", async (c) => {
|
|
95
|
+
const api = getApi(options.collections, c.req.param("collection"));
|
|
96
|
+
const context = await options.resolveContext(c);
|
|
97
|
+
return c.json(await api.find(context));
|
|
98
|
+
});
|
|
99
|
+
router.get("/:collection/search", async (c) => {
|
|
100
|
+
const api = getApi(options.collections, c.req.param("collection"));
|
|
101
|
+
const context = await options.resolveContext(c);
|
|
102
|
+
return c.json(await api.search(context, c.req.query("q") ?? ""));
|
|
103
|
+
});
|
|
104
|
+
router.get("/:collection/:id", async (c) => {
|
|
105
|
+
const api = getApi(options.collections, c.req.param("collection"));
|
|
106
|
+
const context = await options.resolveContext(c);
|
|
107
|
+
return c.json(await api.findByID(context, Number(c.req.param("id"))));
|
|
108
|
+
});
|
|
109
|
+
router.post("/:collection", async (c) => {
|
|
110
|
+
const api = getApi(options.collections, c.req.param("collection"));
|
|
111
|
+
const context = await options.resolveContext(c);
|
|
112
|
+
return c.json(await api.create(context, await c.req.json()), 201);
|
|
113
|
+
});
|
|
114
|
+
router.patch("/:collection/:id", async (c) => {
|
|
115
|
+
const api = getApi(options.collections, c.req.param("collection"));
|
|
116
|
+
const context = await options.resolveContext(c);
|
|
117
|
+
const id = Number(c.req.param("id"));
|
|
118
|
+
return c.json(await api.update(context, id, await c.req.json()));
|
|
119
|
+
});
|
|
120
|
+
router.delete("/:collection/:id", async (c) => {
|
|
121
|
+
const api = getApi(options.collections, c.req.param("collection"));
|
|
122
|
+
const context = await options.resolveContext(c);
|
|
123
|
+
return c.json(await api.deleteByID(context, Number(c.req.param("id"))));
|
|
124
|
+
});
|
|
125
|
+
app.route("/api", router);
|
|
126
|
+
return app;
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
exports.createCmsApiClient = createCmsApiClient;
|
|
130
|
+
exports.mountCmsRoutes = mountCmsRoutes;
|
|
131
|
+
|
|
132
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["CadmusApiError","CadmusAccessDeniedError","CadmusCmsError","Hono"],"sources":["../../src/hono/client.ts","../../src/hono/cms.ts"],"sourcesContent":["// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\nimport { CadmusApiError } from \"../errors.js\";\n\nexport interface CmsApiClientOptions {\n /**\n * Returns the value sent verbatim as the request's `Authorization`\n * header, or `undefined`/`\"\"` to send no `Authorization` header at all.\n * This is the client's *only* auth surface — it never generates, stores,\n * refreshes, or validates a token itself. A bearer token, an OAuth2\n * access token obtained elsewhere, a shared service key — all the\n * caller's problem. No OAuth flow lives here; see EXTENDING.md's\n * provider-interface note if a real OAuth client flow is ever needed —\n * that's separate scope, not an extension of this option.\n */\n getAuthHeader?: () => string | undefined | Promise<string | undefined>;\n}\n\nasync function parseBody(response: Response): Promise<unknown> {\n const text = await response.text();\n if (!text) return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\nfunction errorMessage(body: unknown, status: number): string {\n if (body && typeof body === \"object\" && \"error\" in body) {\n const error = (body as { error: unknown }).error;\n if (typeof error === \"string\") return error;\n }\n return `Request failed with status ${status}`;\n}\n\n/**\n * The client-side counterpart to `mountCmsRoutes` — talks to exactly the\n * REST surface that function mounts (`GET/POST/PATCH/DELETE\n * /api/:collection[...]`), via plain `fetch()`. No Node APIs, works from\n * any environment (browser, Worker, Astro SSR).\n *\n * This is for callers *outside* the Worker process that's actually running\n * the CMS — an Astro island, an external operator's own client, anything\n * that can't call a `LocalApi` in-process. In-process callers (the same\n * Worker's own server functions, Cadmea's own Panel) should keep calling\n * the `LocalApi`/Hono RPC (`hc<AppType>`) directly — this client adds a\n * network hop neither of those needs.\n */\nexport function createCmsApiClient(\n baseUrl: string,\n options: CmsApiClientOptions = {},\n) {\n async function request(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<unknown> {\n const headers: Record<string, string> = {};\n const authHeader = await options.getAuthHeader?.();\n if (authHeader) headers.Authorization = authHeader;\n if (body !== undefined) headers[\"Content-Type\"] = \"application/json\";\n\n let response: Response;\n try {\n response = await fetch(`${baseUrl}/api${path}`, {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n } catch (cause) {\n throw new CadmusApiError(\n `Request to \"${baseUrl}/api${path}\" failed`,\n 0,\n cause,\n );\n }\n\n const parsed = await parseBody(response);\n if (!response.ok) {\n throw new CadmusApiError(\n errorMessage(parsed, response.status),\n response.status,\n parsed,\n );\n }\n return parsed;\n }\n\n return {\n find(collection: string): Promise<unknown[]> {\n return request(\"GET\", `/${collection}`) as Promise<unknown[]>;\n },\n findByID(collection: string, id: number): Promise<unknown> {\n return request(\"GET\", `/${collection}/${id}`);\n },\n search(collection: string, query: string): Promise<unknown[]> {\n return request(\n \"GET\",\n `/${collection}/search?q=${encodeURIComponent(query)}`,\n ) as Promise<unknown[]>;\n },\n create(\n collection: string,\n data: Record<string, unknown>,\n ): Promise<unknown> {\n return request(\"POST\", `/${collection}`, data);\n },\n update(\n collection: string,\n id: number,\n data: Record<string, unknown>,\n ): Promise<unknown> {\n return request(\"PATCH\", `/${collection}/${id}`, data);\n },\n delete(collection: string, id: number): Promise<unknown> {\n return request(\"DELETE\", `/${collection}/${id}`);\n },\n };\n}\n\nexport type CmsApiClient = ReturnType<typeof createCmsApiClient>;\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\nimport type { Context } from \"hono\";\nimport { Hono } from \"hono\";\nimport type { ClientErrorStatusCode } from \"hono/utils/http-status\";\nimport type { LocalApi } from \"../cms/index.js\";\nimport { CadmusAccessDeniedError, CadmusCmsError } from \"../errors.js\";\n\nexport interface CmsRoutesOptions<TContext> {\n // biome-ignore lint/suspicious/noExplicitAny: see above\n collections: Record<string, LocalApi<any, TContext>>;\n /**\n * Resolves the per-request access context passed as the first argument\n * to every Local API call below — called once per request, not once per\n * collection method, so e.g. a session lookup only happens once even\n * though a write touches `create` and its `afterChange` hooks. Cadmus\n * doesn't standardize the context shape (see LocalApi's `TContext`) —\n * the caller's `resolveContext` is the one place that decides it, the\n * same way Cadmea's server functions each call `requireAuthOrThrow()`\n * themselves today.\n */\n resolveContext: (c: Context) => Promise<TContext>;\n}\n\n// Coupled to the exact message strings localApi.ts's notFound() and\n// wrapWriteError() author — both files are Cadmus-internal, so this is\n// matching a contract we control, not arbitrary third-party text. The\n// honest long-term fix is a status/discriminated-code field on\n// CadmusCmsError; flagged as a follow-up, not built here (it would\n// ripple across every existing primitive error).\nfunction statusForError(error: CadmusCmsError): ClientErrorStatusCode {\n if (error instanceof CadmusAccessDeniedError) return 403;\n if (error.message.includes(\"document found with id\")) return 404;\n if (error.message.includes(\"Unique constraint violated\")) return 409;\n return 400;\n}\n\nfunction getApi<TContext>(\n collections: CmsRoutesOptions<TContext>[\"collections\"],\n slug: string,\n // biome-ignore lint/suspicious/noExplicitAny: see CmsRoutesOptions\n): LocalApi<any, TContext> {\n const api = collections[slug];\n if (!api) throw new CadmusCmsError(`Unknown collection \"${slug}\"`);\n return api;\n}\n\n// Mounts a Payload-equivalent REST surface at /api:\n// GET /api/:collection\n// GET /api/:collection/search?q=...\n// GET /api/:collection/:id\n// POST /api/:collection\n// PATCH /api/:collection/:id\n// DELETE /api/:collection/:id\nexport function mountCmsRoutes<TContext>(\n app: Hono,\n options: CmsRoutesOptions<TContext>,\n): Hono {\n const router = new Hono();\n\n router.onError((error, c) => {\n if (error instanceof CadmusCmsError) {\n return c.json({ error: error.message }, statusForError(error));\n }\n throw error;\n });\n\n // `resolveContext` runs once per request, before any Local API call —\n // every route below shares the one resolved context across its method\n // call and that method's own hooks (e.g. create()'s afterChange).\n router.get(\"/:collection\", async (c) => {\n const api = getApi(options.collections, c.req.param(\"collection\"));\n const context = await options.resolveContext(c);\n return c.json(await api.find(context));\n });\n\n // Registered before \"/:collection/:id\" — Hono's router prioritizes a\n // static path segment (\"search\") over a dynamic one (\":id\") regardless\n // of registration order, but the ordering here documents the intent\n // either way: a request for /api/pages/search must never be parsed as\n // findByID with id=\"search\".\n router.get(\"/:collection/search\", async (c) => {\n const api = getApi(options.collections, c.req.param(\"collection\"));\n const context = await options.resolveContext(c);\n return c.json(await api.search(context, c.req.query(\"q\") ?? \"\"));\n });\n\n router.get(\"/:collection/:id\", async (c) => {\n const api = getApi(options.collections, c.req.param(\"collection\"));\n const context = await options.resolveContext(c);\n return c.json(await api.findByID(context, Number(c.req.param(\"id\"))));\n });\n\n router.post(\"/:collection\", async (c) => {\n const api = getApi(options.collections, c.req.param(\"collection\"));\n const context = await options.resolveContext(c);\n return c.json(await api.create(context, await c.req.json()), 201);\n });\n\n router.patch(\"/:collection/:id\", async (c) => {\n const api = getApi(options.collections, c.req.param(\"collection\"));\n const context = await options.resolveContext(c);\n const id = Number(c.req.param(\"id\"));\n return c.json(await api.update(context, id, await c.req.json()));\n });\n\n router.delete(\"/:collection/:id\", async (c) => {\n const api = getApi(options.collections, c.req.param(\"collection\"));\n const context = await options.resolveContext(c);\n return c.json(await api.deleteByID(context, Number(c.req.param(\"id\"))));\n });\n\n app.route(\"/api\", router);\n return app;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { l as LocalApi } from "../index-BUrCSGVb.cjs";
|
|
2
|
+
import { Context, Hono } from "hono";
|
|
3
|
+
|
|
4
|
+
//#region src/hono/client.d.ts
|
|
5
|
+
interface CmsApiClientOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Returns the value sent verbatim as the request's `Authorization`
|
|
8
|
+
* header, or `undefined`/`""` to send no `Authorization` header at all.
|
|
9
|
+
* This is the client's *only* auth surface — it never generates, stores,
|
|
10
|
+
* refreshes, or validates a token itself. A bearer token, an OAuth2
|
|
11
|
+
* access token obtained elsewhere, a shared service key — all the
|
|
12
|
+
* caller's problem. No OAuth flow lives here; see EXTENDING.md's
|
|
13
|
+
* provider-interface note if a real OAuth client flow is ever needed —
|
|
14
|
+
* that's separate scope, not an extension of this option.
|
|
15
|
+
*/
|
|
16
|
+
getAuthHeader?: () => string | undefined | Promise<string | undefined>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The client-side counterpart to `mountCmsRoutes` — talks to exactly the
|
|
20
|
+
* REST surface that function mounts (`GET/POST/PATCH/DELETE
|
|
21
|
+
* /api/:collection[...]`), via plain `fetch()`. No Node APIs, works from
|
|
22
|
+
* any environment (browser, Worker, Astro SSR).
|
|
23
|
+
*
|
|
24
|
+
* This is for callers *outside* the Worker process that's actually running
|
|
25
|
+
* the CMS — an Astro island, an external operator's own client, anything
|
|
26
|
+
* that can't call a `LocalApi` in-process. In-process callers (the same
|
|
27
|
+
* Worker's own server functions, Cadmea's own Panel) should keep calling
|
|
28
|
+
* the `LocalApi`/Hono RPC (`hc<AppType>`) directly — this client adds a
|
|
29
|
+
* network hop neither of those needs.
|
|
30
|
+
*/
|
|
31
|
+
declare function createCmsApiClient(baseUrl: string, options?: CmsApiClientOptions): {
|
|
32
|
+
find(collection: string): Promise<unknown[]>;
|
|
33
|
+
findByID(collection: string, id: number): Promise<unknown>;
|
|
34
|
+
search(collection: string, query: string): Promise<unknown[]>;
|
|
35
|
+
create(collection: string, data: Record<string, unknown>): Promise<unknown>;
|
|
36
|
+
update(collection: string, id: number, data: Record<string, unknown>): Promise<unknown>;
|
|
37
|
+
delete(collection: string, id: number): Promise<unknown>;
|
|
38
|
+
};
|
|
39
|
+
type CmsApiClient = ReturnType<typeof createCmsApiClient>;
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/hono/cms.d.ts
|
|
42
|
+
interface CmsRoutesOptions<TContext> {
|
|
43
|
+
collections: Record<string, LocalApi<any, TContext>>;
|
|
44
|
+
/**
|
|
45
|
+
* Resolves the per-request access context passed as the first argument
|
|
46
|
+
* to every Local API call below — called once per request, not once per
|
|
47
|
+
* collection method, so e.g. a session lookup only happens once even
|
|
48
|
+
* though a write touches `create` and its `afterChange` hooks. Cadmus
|
|
49
|
+
* doesn't standardize the context shape (see LocalApi's `TContext`) —
|
|
50
|
+
* the caller's `resolveContext` is the one place that decides it, the
|
|
51
|
+
* same way Cadmea's server functions each call `requireAuthOrThrow()`
|
|
52
|
+
* themselves today.
|
|
53
|
+
*/
|
|
54
|
+
resolveContext: (c: Context) => Promise<TContext>;
|
|
55
|
+
}
|
|
56
|
+
declare function mountCmsRoutes<TContext>(app: Hono, options: CmsRoutesOptions<TContext>): Hono;
|
|
57
|
+
//#endregion
|
|
58
|
+
export { CmsApiClient, CmsApiClientOptions, CmsRoutesOptions, createCmsApiClient, mountCmsRoutes };
|
|
59
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/hono/client.ts","../../src/hono/cms.ts"],"mappings":";;;;UAKiB,mBAAA;;;;AAAjB;;;;AAWoD;AAkCpD;;EAlCE,aAAA,8BAA2C,OAAO;AAAA;;;;;;;;;;;;;;iBAkCpC,kBAAA,CACd,OAAA,UACA,OAAA,GAAS,mBAAA;4BAuCmB,OAAA;+BAGC,EAAA,WAAe,OAAA;6BAGjB,KAAA,WAAkB,OAAA;6BAOvB,IAAA,EACZ,MAAA,oBACL,OAAA;6BAIiB,EAAA,UACR,IAAA,EACJ,MAAA,oBACL,OAAA;6BAGsB,EAAA,WAAe,OAAA;AAAA;AAAA,KAMhC,YAAA,GAAe,UAAU,QAAQ,kBAAA;;;UCjH5B,gBAAA;EAEf,WAAA,EAAa,MAAA,SAAe,QAAA,MAAc,QAAA;EDNR;;;AAWgB;AAkCpD;;;;;;EC5BE,cAAA,GAAiB,CAAA,EAAG,OAAA,KAAY,OAAA,CAAQ,QAAA;AAAA;AAAA,iBAiC1B,cAAA,WACd,GAAA,EAAK,IAAA,EACL,OAAA,EAAS,gBAAA,CAAiB,QAAA,IACzB,IAAA"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { l as LocalApi } from "../index-BUrCSGVb.js";
|
|
2
|
+
import { Context, Hono } from "hono";
|
|
3
|
+
|
|
4
|
+
//#region src/hono/client.d.ts
|
|
5
|
+
interface CmsApiClientOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Returns the value sent verbatim as the request's `Authorization`
|
|
8
|
+
* header, or `undefined`/`""` to send no `Authorization` header at all.
|
|
9
|
+
* This is the client's *only* auth surface — it never generates, stores,
|
|
10
|
+
* refreshes, or validates a token itself. A bearer token, an OAuth2
|
|
11
|
+
* access token obtained elsewhere, a shared service key — all the
|
|
12
|
+
* caller's problem. No OAuth flow lives here; see EXTENDING.md's
|
|
13
|
+
* provider-interface note if a real OAuth client flow is ever needed —
|
|
14
|
+
* that's separate scope, not an extension of this option.
|
|
15
|
+
*/
|
|
16
|
+
getAuthHeader?: () => string | undefined | Promise<string | undefined>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* The client-side counterpart to `mountCmsRoutes` — talks to exactly the
|
|
20
|
+
* REST surface that function mounts (`GET/POST/PATCH/DELETE
|
|
21
|
+
* /api/:collection[...]`), via plain `fetch()`. No Node APIs, works from
|
|
22
|
+
* any environment (browser, Worker, Astro SSR).
|
|
23
|
+
*
|
|
24
|
+
* This is for callers *outside* the Worker process that's actually running
|
|
25
|
+
* the CMS — an Astro island, an external operator's own client, anything
|
|
26
|
+
* that can't call a `LocalApi` in-process. In-process callers (the same
|
|
27
|
+
* Worker's own server functions, Cadmea's own Panel) should keep calling
|
|
28
|
+
* the `LocalApi`/Hono RPC (`hc<AppType>`) directly — this client adds a
|
|
29
|
+
* network hop neither of those needs.
|
|
30
|
+
*/
|
|
31
|
+
declare function createCmsApiClient(baseUrl: string, options?: CmsApiClientOptions): {
|
|
32
|
+
find(collection: string): Promise<unknown[]>;
|
|
33
|
+
findByID(collection: string, id: number): Promise<unknown>;
|
|
34
|
+
search(collection: string, query: string): Promise<unknown[]>;
|
|
35
|
+
create(collection: string, data: Record<string, unknown>): Promise<unknown>;
|
|
36
|
+
update(collection: string, id: number, data: Record<string, unknown>): Promise<unknown>;
|
|
37
|
+
delete(collection: string, id: number): Promise<unknown>;
|
|
38
|
+
};
|
|
39
|
+
type CmsApiClient = ReturnType<typeof createCmsApiClient>;
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/hono/cms.d.ts
|
|
42
|
+
interface CmsRoutesOptions<TContext> {
|
|
43
|
+
collections: Record<string, LocalApi<any, TContext>>;
|
|
44
|
+
/**
|
|
45
|
+
* Resolves the per-request access context passed as the first argument
|
|
46
|
+
* to every Local API call below — called once per request, not once per
|
|
47
|
+
* collection method, so e.g. a session lookup only happens once even
|
|
48
|
+
* though a write touches `create` and its `afterChange` hooks. Cadmus
|
|
49
|
+
* doesn't standardize the context shape (see LocalApi's `TContext`) —
|
|
50
|
+
* the caller's `resolveContext` is the one place that decides it, the
|
|
51
|
+
* same way Cadmea's server functions each call `requireAuthOrThrow()`
|
|
52
|
+
* themselves today.
|
|
53
|
+
*/
|
|
54
|
+
resolveContext: (c: Context) => Promise<TContext>;
|
|
55
|
+
}
|
|
56
|
+
declare function mountCmsRoutes<TContext>(app: Hono, options: CmsRoutesOptions<TContext>): Hono;
|
|
57
|
+
//#endregion
|
|
58
|
+
export { CmsApiClient, CmsApiClientOptions, CmsRoutesOptions, createCmsApiClient, mountCmsRoutes };
|
|
59
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/hono/client.ts","../../src/hono/cms.ts"],"mappings":";;;;UAKiB,mBAAA;;;;AAAjB;;;;AAWoD;AAkCpD;;EAlCE,aAAA,8BAA2C,OAAO;AAAA;;;;;;;;;;;;;;iBAkCpC,kBAAA,CACd,OAAA,UACA,OAAA,GAAS,mBAAA;4BAuCmB,OAAA;+BAGC,EAAA,WAAe,OAAA;6BAGjB,KAAA,WAAkB,OAAA;6BAOvB,IAAA,EACZ,MAAA,oBACL,OAAA;6BAIiB,EAAA,UACR,IAAA,EACJ,MAAA,oBACL,OAAA;6BAGsB,EAAA,WAAe,OAAA;AAAA;AAAA,KAMhC,YAAA,GAAe,UAAU,QAAQ,kBAAA;;;UCjH5B,gBAAA;EAEf,WAAA,EAAa,MAAA,SAAe,QAAA,MAAc,QAAA;EDNR;;;AAWgB;AAkCpD;;;;;;EC5BE,cAAA,GAAiB,CAAA,EAAG,OAAA,KAAY,OAAA,CAAQ,QAAA;AAAA;AAAA,iBAiC1B,cAAA,WACd,GAAA,EAAK,IAAA,EACL,OAAA,EAAS,gBAAA,CAAiB,QAAA,IACzB,IAAA"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { a as CadmusCmsError, n as CadmusApiError, t as CadmusAccessDeniedError } from "../errors-mZIqZJO4.js";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
//#region src/hono/client.ts
|
|
4
|
+
async function parseBody(response) {
|
|
5
|
+
const text = await response.text();
|
|
6
|
+
if (!text) return void 0;
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(text);
|
|
9
|
+
} catch {
|
|
10
|
+
return text;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function errorMessage(body, status) {
|
|
14
|
+
if (body && typeof body === "object" && "error" in body) {
|
|
15
|
+
const error = body.error;
|
|
16
|
+
if (typeof error === "string") return error;
|
|
17
|
+
}
|
|
18
|
+
return `Request failed with status ${status}`;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The client-side counterpart to `mountCmsRoutes` — talks to exactly the
|
|
22
|
+
* REST surface that function mounts (`GET/POST/PATCH/DELETE
|
|
23
|
+
* /api/:collection[...]`), via plain `fetch()`. No Node APIs, works from
|
|
24
|
+
* any environment (browser, Worker, Astro SSR).
|
|
25
|
+
*
|
|
26
|
+
* This is for callers *outside* the Worker process that's actually running
|
|
27
|
+
* the CMS — an Astro island, an external operator's own client, anything
|
|
28
|
+
* that can't call a `LocalApi` in-process. In-process callers (the same
|
|
29
|
+
* Worker's own server functions, Cadmea's own Panel) should keep calling
|
|
30
|
+
* the `LocalApi`/Hono RPC (`hc<AppType>`) directly — this client adds a
|
|
31
|
+
* network hop neither of those needs.
|
|
32
|
+
*/
|
|
33
|
+
function createCmsApiClient(baseUrl, options = {}) {
|
|
34
|
+
async function request(method, path, body) {
|
|
35
|
+
const headers = {};
|
|
36
|
+
const authHeader = await options.getAuthHeader?.();
|
|
37
|
+
if (authHeader) headers.Authorization = authHeader;
|
|
38
|
+
if (body !== void 0) headers["Content-Type"] = "application/json";
|
|
39
|
+
let response;
|
|
40
|
+
try {
|
|
41
|
+
response = await fetch(`${baseUrl}/api${path}`, {
|
|
42
|
+
method,
|
|
43
|
+
headers,
|
|
44
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
45
|
+
});
|
|
46
|
+
} catch (cause) {
|
|
47
|
+
throw new CadmusApiError(`Request to "${baseUrl}/api${path}" failed`, 0, cause);
|
|
48
|
+
}
|
|
49
|
+
const parsed = await parseBody(response);
|
|
50
|
+
if (!response.ok) throw new CadmusApiError(errorMessage(parsed, response.status), response.status, parsed);
|
|
51
|
+
return parsed;
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
find(collection) {
|
|
55
|
+
return request("GET", `/${collection}`);
|
|
56
|
+
},
|
|
57
|
+
findByID(collection, id) {
|
|
58
|
+
return request("GET", `/${collection}/${id}`);
|
|
59
|
+
},
|
|
60
|
+
search(collection, query) {
|
|
61
|
+
return request("GET", `/${collection}/search?q=${encodeURIComponent(query)}`);
|
|
62
|
+
},
|
|
63
|
+
create(collection, data) {
|
|
64
|
+
return request("POST", `/${collection}`, data);
|
|
65
|
+
},
|
|
66
|
+
update(collection, id, data) {
|
|
67
|
+
return request("PATCH", `/${collection}/${id}`, data);
|
|
68
|
+
},
|
|
69
|
+
delete(collection, id) {
|
|
70
|
+
return request("DELETE", `/${collection}/${id}`);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/hono/cms.ts
|
|
76
|
+
function statusForError(error) {
|
|
77
|
+
if (error instanceof CadmusAccessDeniedError) return 403;
|
|
78
|
+
if (error.message.includes("document found with id")) return 404;
|
|
79
|
+
if (error.message.includes("Unique constraint violated")) return 409;
|
|
80
|
+
return 400;
|
|
81
|
+
}
|
|
82
|
+
function getApi(collections, slug) {
|
|
83
|
+
const api = collections[slug];
|
|
84
|
+
if (!api) throw new CadmusCmsError(`Unknown collection "${slug}"`);
|
|
85
|
+
return api;
|
|
86
|
+
}
|
|
87
|
+
function mountCmsRoutes(app, options) {
|
|
88
|
+
const router = new Hono();
|
|
89
|
+
router.onError((error, c) => {
|
|
90
|
+
if (error instanceof CadmusCmsError) return c.json({ error: error.message }, statusForError(error));
|
|
91
|
+
throw error;
|
|
92
|
+
});
|
|
93
|
+
router.get("/:collection", async (c) => {
|
|
94
|
+
const api = getApi(options.collections, c.req.param("collection"));
|
|
95
|
+
const context = await options.resolveContext(c);
|
|
96
|
+
return c.json(await api.find(context));
|
|
97
|
+
});
|
|
98
|
+
router.get("/:collection/search", async (c) => {
|
|
99
|
+
const api = getApi(options.collections, c.req.param("collection"));
|
|
100
|
+
const context = await options.resolveContext(c);
|
|
101
|
+
return c.json(await api.search(context, c.req.query("q") ?? ""));
|
|
102
|
+
});
|
|
103
|
+
router.get("/:collection/:id", async (c) => {
|
|
104
|
+
const api = getApi(options.collections, c.req.param("collection"));
|
|
105
|
+
const context = await options.resolveContext(c);
|
|
106
|
+
return c.json(await api.findByID(context, Number(c.req.param("id"))));
|
|
107
|
+
});
|
|
108
|
+
router.post("/:collection", async (c) => {
|
|
109
|
+
const api = getApi(options.collections, c.req.param("collection"));
|
|
110
|
+
const context = await options.resolveContext(c);
|
|
111
|
+
return c.json(await api.create(context, await c.req.json()), 201);
|
|
112
|
+
});
|
|
113
|
+
router.patch("/:collection/:id", async (c) => {
|
|
114
|
+
const api = getApi(options.collections, c.req.param("collection"));
|
|
115
|
+
const context = await options.resolveContext(c);
|
|
116
|
+
const id = Number(c.req.param("id"));
|
|
117
|
+
return c.json(await api.update(context, id, await c.req.json()));
|
|
118
|
+
});
|
|
119
|
+
router.delete("/:collection/:id", async (c) => {
|
|
120
|
+
const api = getApi(options.collections, c.req.param("collection"));
|
|
121
|
+
const context = await options.resolveContext(c);
|
|
122
|
+
return c.json(await api.deleteByID(context, Number(c.req.param("id"))));
|
|
123
|
+
});
|
|
124
|
+
app.route("/api", router);
|
|
125
|
+
return app;
|
|
126
|
+
}
|
|
127
|
+
//#endregion
|
|
128
|
+
export { createCmsApiClient, mountCmsRoutes };
|
|
129
|
+
|
|
130
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/hono/client.ts","../../src/hono/cms.ts"],"sourcesContent":["// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\nimport { CadmusApiError } from \"../errors.js\";\n\nexport interface CmsApiClientOptions {\n /**\n * Returns the value sent verbatim as the request's `Authorization`\n * header, or `undefined`/`\"\"` to send no `Authorization` header at all.\n * This is the client's *only* auth surface — it never generates, stores,\n * refreshes, or validates a token itself. A bearer token, an OAuth2\n * access token obtained elsewhere, a shared service key — all the\n * caller's problem. No OAuth flow lives here; see EXTENDING.md's\n * provider-interface note if a real OAuth client flow is ever needed —\n * that's separate scope, not an extension of this option.\n */\n getAuthHeader?: () => string | undefined | Promise<string | undefined>;\n}\n\nasync function parseBody(response: Response): Promise<unknown> {\n const text = await response.text();\n if (!text) return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\nfunction errorMessage(body: unknown, status: number): string {\n if (body && typeof body === \"object\" && \"error\" in body) {\n const error = (body as { error: unknown }).error;\n if (typeof error === \"string\") return error;\n }\n return `Request failed with status ${status}`;\n}\n\n/**\n * The client-side counterpart to `mountCmsRoutes` — talks to exactly the\n * REST surface that function mounts (`GET/POST/PATCH/DELETE\n * /api/:collection[...]`), via plain `fetch()`. No Node APIs, works from\n * any environment (browser, Worker, Astro SSR).\n *\n * This is for callers *outside* the Worker process that's actually running\n * the CMS — an Astro island, an external operator's own client, anything\n * that can't call a `LocalApi` in-process. In-process callers (the same\n * Worker's own server functions, Cadmea's own Panel) should keep calling\n * the `LocalApi`/Hono RPC (`hc<AppType>`) directly — this client adds a\n * network hop neither of those needs.\n */\nexport function createCmsApiClient(\n baseUrl: string,\n options: CmsApiClientOptions = {},\n) {\n async function request(\n method: string,\n path: string,\n body?: unknown,\n ): Promise<unknown> {\n const headers: Record<string, string> = {};\n const authHeader = await options.getAuthHeader?.();\n if (authHeader) headers.Authorization = authHeader;\n if (body !== undefined) headers[\"Content-Type\"] = \"application/json\";\n\n let response: Response;\n try {\n response = await fetch(`${baseUrl}/api${path}`, {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n });\n } catch (cause) {\n throw new CadmusApiError(\n `Request to \"${baseUrl}/api${path}\" failed`,\n 0,\n cause,\n );\n }\n\n const parsed = await parseBody(response);\n if (!response.ok) {\n throw new CadmusApiError(\n errorMessage(parsed, response.status),\n response.status,\n parsed,\n );\n }\n return parsed;\n }\n\n return {\n find(collection: string): Promise<unknown[]> {\n return request(\"GET\", `/${collection}`) as Promise<unknown[]>;\n },\n findByID(collection: string, id: number): Promise<unknown> {\n return request(\"GET\", `/${collection}/${id}`);\n },\n search(collection: string, query: string): Promise<unknown[]> {\n return request(\n \"GET\",\n `/${collection}/search?q=${encodeURIComponent(query)}`,\n ) as Promise<unknown[]>;\n },\n create(\n collection: string,\n data: Record<string, unknown>,\n ): Promise<unknown> {\n return request(\"POST\", `/${collection}`, data);\n },\n update(\n collection: string,\n id: number,\n data: Record<string, unknown>,\n ): Promise<unknown> {\n return request(\"PATCH\", `/${collection}/${id}`, data);\n },\n delete(collection: string, id: number): Promise<unknown> {\n return request(\"DELETE\", `/${collection}/${id}`);\n },\n };\n}\n\nexport type CmsApiClient = ReturnType<typeof createCmsApiClient>;\n","// Copyright (c) 2026 BowenLabs. All rights reserved.\n// Cadmus is MIT licensed. See LICENSE in the repo root.\n\nimport type { Context } from \"hono\";\nimport { Hono } from \"hono\";\nimport type { ClientErrorStatusCode } from \"hono/utils/http-status\";\nimport type { LocalApi } from \"../cms/index.js\";\nimport { CadmusAccessDeniedError, CadmusCmsError } from \"../errors.js\";\n\nexport interface CmsRoutesOptions<TContext> {\n // biome-ignore lint/suspicious/noExplicitAny: see above\n collections: Record<string, LocalApi<any, TContext>>;\n /**\n * Resolves the per-request access context passed as the first argument\n * to every Local API call below — called once per request, not once per\n * collection method, so e.g. a session lookup only happens once even\n * though a write touches `create` and its `afterChange` hooks. Cadmus\n * doesn't standardize the context shape (see LocalApi's `TContext`) —\n * the caller's `resolveContext` is the one place that decides it, the\n * same way Cadmea's server functions each call `requireAuthOrThrow()`\n * themselves today.\n */\n resolveContext: (c: Context) => Promise<TContext>;\n}\n\n// Coupled to the exact message strings localApi.ts's notFound() and\n// wrapWriteError() author — both files are Cadmus-internal, so this is\n// matching a contract we control, not arbitrary third-party text. The\n// honest long-term fix is a status/discriminated-code field on\n// CadmusCmsError; flagged as a follow-up, not built here (it would\n// ripple across every existing primitive error).\nfunction statusForError(error: CadmusCmsError): ClientErrorStatusCode {\n if (error instanceof CadmusAccessDeniedError) return 403;\n if (error.message.includes(\"document found with id\")) return 404;\n if (error.message.includes(\"Unique constraint violated\")) return 409;\n return 400;\n}\n\nfunction getApi<TContext>(\n collections: CmsRoutesOptions<TContext>[\"collections\"],\n slug: string,\n // biome-ignore lint/suspicious/noExplicitAny: see CmsRoutesOptions\n): LocalApi<any, TContext> {\n const api = collections[slug];\n if (!api) throw new CadmusCmsError(`Unknown collection \"${slug}\"`);\n return api;\n}\n\n// Mounts a Payload-equivalent REST surface at /api:\n// GET /api/:collection\n// GET /api/:collection/search?q=...\n// GET /api/:collection/:id\n// POST /api/:collection\n// PATCH /api/:collection/:id\n// DELETE /api/:collection/:id\nexport function mountCmsRoutes<TContext>(\n app: Hono,\n options: CmsRoutesOptions<TContext>,\n): Hono {\n const router = new Hono();\n\n router.onError((error, c) => {\n if (error instanceof CadmusCmsError) {\n return c.json({ error: error.message }, statusForError(error));\n }\n throw error;\n });\n\n // `resolveContext` runs once per request, before any Local API call —\n // every route below shares the one resolved context across its method\n // call and that method's own hooks (e.g. create()'s afterChange).\n router.get(\"/:collection\", async (c) => {\n const api = getApi(options.collections, c.req.param(\"collection\"));\n const context = await options.resolveContext(c);\n return c.json(await api.find(context));\n });\n\n // Registered before \"/:collection/:id\" — Hono's router prioritizes a\n // static path segment (\"search\") over a dynamic one (\":id\") regardless\n // of registration order, but the ordering here documents the intent\n // either way: a request for /api/pages/search must never be parsed as\n // findByID with id=\"search\".\n router.get(\"/:collection/search\", async (c) => {\n const api = getApi(options.collections, c.req.param(\"collection\"));\n const context = await options.resolveContext(c);\n return c.json(await api.search(context, c.req.query(\"q\") ?? \"\"));\n });\n\n router.get(\"/:collection/:id\", async (c) => {\n const api = getApi(options.collections, c.req.param(\"collection\"));\n const context = await options.resolveContext(c);\n return c.json(await api.findByID(context, Number(c.req.param(\"id\"))));\n });\n\n router.post(\"/:collection\", async (c) => {\n const api = getApi(options.collections, c.req.param(\"collection\"));\n const context = await options.resolveContext(c);\n return c.json(await api.create(context, await c.req.json()), 201);\n });\n\n router.patch(\"/:collection/:id\", async (c) => {\n const api = getApi(options.collections, c.req.param(\"collection\"));\n const context = await options.resolveContext(c);\n const id = Number(c.req.param(\"id\"));\n return c.json(await api.update(context, id, await c.req.json()));\n });\n\n router.delete(\"/:collection/:id\", async (c) => {\n const api = getApi(options.collections, c.req.param(\"collection\"));\n const context = await options.resolveContext(c);\n return c.json(await api.deleteByID(context, Number(c.req.param(\"id\"))));\n });\n\n app.route(\"/api\", router);\n return app;\n}\n"],"mappings":"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"}
|