@thebes/cadmus 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cms/index.cjs +689 -3
- package/dist/cms/index.cjs.map +1 -1
- package/dist/cms/index.d.cts +2 -2
- package/dist/cms/index.d.ts +2 -2
- package/dist/cms/index.js +671 -5
- package/dist/cms/index.js.map +1 -1
- package/dist/email/index.cjs +1 -1
- package/dist/email/index.js +1 -1
- package/dist/{errors-CW6Lz0AQ.cjs → errors-BhoibM6Z.cjs} +24 -1
- package/dist/{errors-CW6Lz0AQ.cjs.map → errors-BhoibM6Z.cjs.map} +1 -1
- package/dist/{errors-mZIqZJO4.js → errors-C8SqkFjl.js} +19 -2
- package/dist/{errors-mZIqZJO4.js.map → errors-C8SqkFjl.js.map} +1 -1
- package/dist/hono/index.cjs +6 -1
- package/dist/hono/index.cjs.map +1 -1
- package/dist/hono/index.d.cts +1 -1
- package/dist/hono/index.d.cts.map +1 -1
- package/dist/hono/index.d.ts +1 -1
- package/dist/hono/index.d.ts.map +1 -1
- package/dist/hono/index.js +6 -1
- package/dist/hono/index.js.map +1 -1
- package/dist/index-sB3YOadC.d.cts +1304 -0
- package/dist/index-sB3YOadC.d.cts.map +1 -0
- package/dist/index-sB3YOadC.d.ts +1304 -0
- package/dist/index-sB3YOadC.d.ts.map +1 -0
- package/dist/index.cjs +22 -1
- package/dist/index.d.cts +3 -89
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +3 -89
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/queues/index.cjs +1 -1
- package/dist/queues/index.js +1 -1
- package/dist/rate-limit/index.cjs +1 -1
- package/dist/rate-limit/index.js +1 -1
- package/dist/session/index.cjs +1 -1
- package/dist/session/index.js +1 -1
- package/dist/storage/index.cjs +1 -1
- package/dist/storage/index.cjs.map +1 -1
- package/dist/storage/index.d.cts +31 -2
- package/dist/storage/index.d.cts.map +1 -1
- package/dist/storage/index.d.ts +31 -2
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +1 -1
- package/dist/storage/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/index-BUrCSGVb.d.cts +0 -616
- package/dist/index-BUrCSGVb.d.cts.map +0 -1
- package/dist/index-BUrCSGVb.d.ts +0 -616
- package/dist/index-BUrCSGVb.d.ts.map +0 -1
package/dist/email/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_errors = require("../errors-
|
|
2
|
+
const require_errors = require("../errors-BhoibM6Z.cjs");
|
|
3
3
|
let cloudflare_email = require("cloudflare:email");
|
|
4
4
|
let mimetext_browser = require("mimetext/browser");
|
|
5
5
|
//#region src/email/index.ts
|
package/dist/email/index.js
CHANGED
|
@@ -103,6 +103,23 @@ var CadmusAccessDeniedError = class extends CadmusCmsError {
|
|
|
103
103
|
}
|
|
104
104
|
};
|
|
105
105
|
/**
|
|
106
|
+
* Thrown by createLocalApi when a collection's field-validation rules
|
|
107
|
+
* (Sanity-style chainable `Rule` API — see cms/validation.ts) reject a
|
|
108
|
+
* create/update. Carries the structured `violations` so the studio can
|
|
109
|
+
* surface per-field messages, and `mountCmsRoutes` can map it to HTTP 422
|
|
110
|
+
* by `instanceof` rather than message matching. A subclass of
|
|
111
|
+
* CadmusCmsError, so existing `instanceof CadmusCmsError` handling still
|
|
112
|
+
* catches it. Only `"error"`-severity violations are ever thrown.
|
|
113
|
+
*/
|
|
114
|
+
var CadmusValidationError = class extends CadmusCmsError {
|
|
115
|
+
violations;
|
|
116
|
+
constructor(message, violations, cause) {
|
|
117
|
+
super(message, cause);
|
|
118
|
+
this.violations = violations;
|
|
119
|
+
this.name = "CadmusValidationError";
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
106
123
|
* Thrown by @thebes/cadmus/hono's `createCmsApiClient` when a request
|
|
107
124
|
* against a `mountCmsRoutes` surface returns a non-2xx response. Carries
|
|
108
125
|
* the HTTP status and parsed body so callers can branch on `status`
|
|
@@ -192,5 +209,11 @@ Object.defineProperty(exports, "CadmusStorageError", {
|
|
|
192
209
|
return CadmusStorageError;
|
|
193
210
|
}
|
|
194
211
|
});
|
|
212
|
+
Object.defineProperty(exports, "CadmusValidationError", {
|
|
213
|
+
enumerable: true,
|
|
214
|
+
get: function() {
|
|
215
|
+
return CadmusValidationError;
|
|
216
|
+
}
|
|
217
|
+
});
|
|
195
218
|
|
|
196
|
-
//# sourceMappingURL=errors-
|
|
219
|
+
//# sourceMappingURL=errors-BhoibM6Z.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors-
|
|
1
|
+
{"version":3,"file":"errors-BhoibM6Z.cjs","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 * One failed field-validation rule (issue #16). `path` is the field's key\n * (flattened, e.g. `shippingAddress_city` for a group subfield). `severity`\n * lets a rule warn without blocking the write — only `\"error\"` violations\n * cause createLocalApi to throw; `\"warning\"` ones are carried through for\n * the studio to surface non-blockingly.\n */\nexport interface ValidationViolation {\n path: string;\n message: string;\n severity: \"error\" | \"warning\";\n}\n\n/**\n * Thrown by createLocalApi when a collection's field-validation rules\n * (Sanity-style chainable `Rule` API — see cms/validation.ts) reject a\n * create/update. Carries the structured `violations` so the studio can\n * surface per-field messages, and `mountCmsRoutes` can map it to HTTP 422\n * by `instanceof` rather than message matching. A subclass of\n * CadmusCmsError, so existing `instanceof CadmusCmsError` handling still\n * catches it. Only `\"error\"`-severity violations are ever thrown.\n */\nexport class CadmusValidationError extends CadmusCmsError {\n constructor(\n message: string,\n public readonly violations: ValidationViolation[],\n cause?: unknown,\n ) {\n super(message, cause);\n this.name = \"CadmusValidationError\";\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;;;;;;;;;;AAwBA,IAAa,wBAAb,cAA2C,eAAe;CAGtC;CAFlB,YACE,SACA,YACA,OACA;EACA,MAAM,SAAS,KAAK;EAHJ,KAAA,aAAA;EAIhB,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"}
|
|
@@ -103,6 +103,23 @@ var CadmusAccessDeniedError = class extends CadmusCmsError {
|
|
|
103
103
|
}
|
|
104
104
|
};
|
|
105
105
|
/**
|
|
106
|
+
* Thrown by createLocalApi when a collection's field-validation rules
|
|
107
|
+
* (Sanity-style chainable `Rule` API — see cms/validation.ts) reject a
|
|
108
|
+
* create/update. Carries the structured `violations` so the studio can
|
|
109
|
+
* surface per-field messages, and `mountCmsRoutes` can map it to HTTP 422
|
|
110
|
+
* by `instanceof` rather than message matching. A subclass of
|
|
111
|
+
* CadmusCmsError, so existing `instanceof CadmusCmsError` handling still
|
|
112
|
+
* catches it. Only `"error"`-severity violations are ever thrown.
|
|
113
|
+
*/
|
|
114
|
+
var CadmusValidationError = class extends CadmusCmsError {
|
|
115
|
+
violations;
|
|
116
|
+
constructor(message, violations, cause) {
|
|
117
|
+
super(message, cause);
|
|
118
|
+
this.violations = violations;
|
|
119
|
+
this.name = "CadmusValidationError";
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
106
123
|
* Thrown by @thebes/cadmus/hono's `createCmsApiClient` when a request
|
|
107
124
|
* against a `mountCmsRoutes` surface returns a non-2xx response. Carries
|
|
108
125
|
* the HTTP status and parsed body so callers can branch on `status`
|
|
@@ -120,6 +137,6 @@ var CadmusApiError = class extends CadmusError {
|
|
|
120
137
|
}
|
|
121
138
|
};
|
|
122
139
|
//#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 };
|
|
140
|
+
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, CadmusValidationError as p, CadmusAuthError as r, CadmusEmailError as s, CadmusAccessDeniedError as t, CadmusRateLimitError as u };
|
|
124
141
|
|
|
125
|
-
//# sourceMappingURL=errors-
|
|
142
|
+
//# sourceMappingURL=errors-C8SqkFjl.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors-
|
|
1
|
+
{"version":3,"file":"errors-C8SqkFjl.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 * One failed field-validation rule (issue #16). `path` is the field's key\n * (flattened, e.g. `shippingAddress_city` for a group subfield). `severity`\n * lets a rule warn without blocking the write — only `\"error\"` violations\n * cause createLocalApi to throw; `\"warning\"` ones are carried through for\n * the studio to surface non-blockingly.\n */\nexport interface ValidationViolation {\n path: string;\n message: string;\n severity: \"error\" | \"warning\";\n}\n\n/**\n * Thrown by createLocalApi when a collection's field-validation rules\n * (Sanity-style chainable `Rule` API — see cms/validation.ts) reject a\n * create/update. Carries the structured `violations` so the studio can\n * surface per-field messages, and `mountCmsRoutes` can map it to HTTP 422\n * by `instanceof` rather than message matching. A subclass of\n * CadmusCmsError, so existing `instanceof CadmusCmsError` handling still\n * catches it. Only `\"error\"`-severity violations are ever thrown.\n */\nexport class CadmusValidationError extends CadmusCmsError {\n constructor(\n message: string,\n public readonly violations: ValidationViolation[],\n cause?: unknown,\n ) {\n super(message, cause);\n this.name = \"CadmusValidationError\";\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;;;;;;;;;;AAwBA,IAAa,wBAAb,cAA2C,eAAe;CAGtC;CAFlB,YACE,SACA,YACA,OACA;EACA,MAAM,SAAS,KAAK;EAHJ,KAAA,aAAA;EAIhB,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"}
|
package/dist/hono/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_errors = require("../errors-
|
|
2
|
+
const require_errors = require("../errors-BhoibM6Z.cjs");
|
|
3
3
|
let hono = require("hono");
|
|
4
4
|
//#region src/hono/client.ts
|
|
5
5
|
async function parseBody(response) {
|
|
@@ -76,6 +76,7 @@ function createCmsApiClient(baseUrl, options = {}) {
|
|
|
76
76
|
//#region src/hono/cms.ts
|
|
77
77
|
function statusForError(error) {
|
|
78
78
|
if (error instanceof require_errors.CadmusAccessDeniedError) return 403;
|
|
79
|
+
if (error instanceof require_errors.CadmusValidationError) return 422;
|
|
79
80
|
if (error.message.includes("document found with id")) return 404;
|
|
80
81
|
if (error.message.includes("Unique constraint violated")) return 409;
|
|
81
82
|
return 400;
|
|
@@ -88,6 +89,10 @@ function getApi(collections, slug) {
|
|
|
88
89
|
function mountCmsRoutes(app, options) {
|
|
89
90
|
const router = new hono.Hono();
|
|
90
91
|
router.onError((error, c) => {
|
|
92
|
+
if (error instanceof require_errors.CadmusValidationError) return c.json({
|
|
93
|
+
error: error.message,
|
|
94
|
+
violations: error.violations
|
|
95
|
+
}, statusForError(error));
|
|
91
96
|
if (error instanceof require_errors.CadmusCmsError) return c.json({ error: error.message }, statusForError(error));
|
|
92
97
|
throw error;
|
|
93
98
|
});
|
package/dist/hono/index.cjs.map
CHANGED
|
@@ -1 +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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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["CadmusApiError","CadmusAccessDeniedError","CadmusValidationError","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 {\n CadmusAccessDeniedError,\n CadmusCmsError,\n CadmusValidationError,\n} 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 // Field-validation failures are an unprocessable entity, not a malformed\n // request — 422 so clients can distinguish \"your input broke a rule\" from\n // a generic 400 and read the structured `violations` off the body.\n if (error instanceof CadmusValidationError) return 422;\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 CadmusValidationError) {\n // Surface per-field violations so a studio client can map them back to\n // form fields rather than re-parsing the summary message.\n return c.json(\n { error: error.message, violations: error.violations },\n statusForError(error),\n );\n }\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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"}
|
package/dist/hono/index.d.cts
CHANGED
|
@@ -1 +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;;;
|
|
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;;;UC7G5B,gBAAA;EAEf,WAAA,EAAa,MAAA,SAAe,QAAA,MAAc,QAAA;EDVR;;;AAWgB;AAkCpD;;;;;;ECxBE,cAAA,GAAiB,CAAA,EAAG,OAAA,KAAY,OAAA,CAAQ,QAAA;AAAA;AAAA,iBAqC1B,cAAA,WACd,GAAA,EAAK,IAAA,EACL,OAAA,EAAS,gBAAA,CAAiB,QAAA,IACzB,IAAA"}
|
package/dist/hono/index.d.ts
CHANGED
package/dist/hono/index.d.ts.map
CHANGED
|
@@ -1 +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;;;
|
|
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;;;UC7G5B,gBAAA;EAEf,WAAA,EAAa,MAAA,SAAe,QAAA,MAAc,QAAA;EDVR;;;AAWgB;AAkCpD;;;;;;ECxBE,cAAA,GAAiB,CAAA,EAAG,OAAA,KAAY,OAAA,CAAQ,QAAA;AAAA;AAAA,iBAqC1B,cAAA,WACd,GAAA,EAAK,IAAA,EACL,OAAA,EAAS,gBAAA,CAAiB,QAAA,IACzB,IAAA"}
|
package/dist/hono/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as CadmusCmsError, n as CadmusApiError, t as CadmusAccessDeniedError } from "../errors-
|
|
1
|
+
import { a as CadmusCmsError, n as CadmusApiError, p as CadmusValidationError, t as CadmusAccessDeniedError } from "../errors-C8SqkFjl.js";
|
|
2
2
|
import { Hono } from "hono";
|
|
3
3
|
//#region src/hono/client.ts
|
|
4
4
|
async function parseBody(response) {
|
|
@@ -75,6 +75,7 @@ function createCmsApiClient(baseUrl, options = {}) {
|
|
|
75
75
|
//#region src/hono/cms.ts
|
|
76
76
|
function statusForError(error) {
|
|
77
77
|
if (error instanceof CadmusAccessDeniedError) return 403;
|
|
78
|
+
if (error instanceof CadmusValidationError) return 422;
|
|
78
79
|
if (error.message.includes("document found with id")) return 404;
|
|
79
80
|
if (error.message.includes("Unique constraint violated")) return 409;
|
|
80
81
|
return 400;
|
|
@@ -87,6 +88,10 @@ function getApi(collections, slug) {
|
|
|
87
88
|
function mountCmsRoutes(app, options) {
|
|
88
89
|
const router = new Hono();
|
|
89
90
|
router.onError((error, c) => {
|
|
91
|
+
if (error instanceof CadmusValidationError) return c.json({
|
|
92
|
+
error: error.message,
|
|
93
|
+
violations: error.violations
|
|
94
|
+
}, statusForError(error));
|
|
90
95
|
if (error instanceof CadmusCmsError) return c.json({ error: error.message }, statusForError(error));
|
|
91
96
|
throw error;
|
|
92
97
|
});
|
package/dist/hono/index.js.map
CHANGED
|
@@ -1 +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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"}
|
|
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 {\n CadmusAccessDeniedError,\n CadmusCmsError,\n CadmusValidationError,\n} 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 // Field-validation failures are an unprocessable entity, not a malformed\n // request — 422 so clients can distinguish \"your input broke a rule\" from\n // a generic 400 and read the structured `violations` off the body.\n if (error instanceof CadmusValidationError) return 422;\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 CadmusValidationError) {\n // Surface per-field violations so a studio client can map them back to\n // form fields rather than re-parsing the summary message.\n return c.json(\n { error: error.message, violations: error.violations },\n statusForError(error),\n );\n }\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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"}
|