@adrianhall/cloudflare-toolkit 0.0.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 +59 -0
- package/THIRD-PARTY-NOTICES.md +75 -0
- package/dist/cli/generate-wrangler-types/index.d.ts +1 -0
- package/dist/cli/generate-wrangler-types/index.js +329 -0
- package/dist/cli/generate-wrangler-types/index.js.map +1 -0
- package/dist/error-CLYcAvBM.d.ts +28 -0
- package/dist/errors/index.d.ts +2 -0
- package/dist/errors/index.js +3 -0
- package/dist/errors-Ciipq_zr.js +58 -0
- package/dist/errors-Ciipq_zr.js.map +1 -0
- package/dist/factory-BI5gVL_P.js +220 -0
- package/dist/factory-BI5gVL_P.js.map +1 -0
- package/dist/generators-D8WWEHa1.js +165 -0
- package/dist/generators-D8WWEHa1.js.map +1 -0
- package/dist/guards/index.d.ts +2 -0
- package/dist/guards/index.js +2 -0
- package/dist/guards-6K1CVAr5.js +61 -0
- package/dist/guards-6K1CVAr5.js.map +1 -0
- package/dist/hono/index.d.ts +347 -0
- package/dist/hono/index.js +307 -0
- package/dist/hono/index.js.map +1 -0
- package/dist/index-434HN8jN.d.ts +43 -0
- package/dist/index-Byl-ZrCy.d.ts +138 -0
- package/dist/index-CUICemFw.d.ts +129 -0
- package/dist/index-DRIhR-Xn.d.ts +81 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/jwt-BvuKtvby.js +328 -0
- package/dist/jwt-BvuKtvby.js.map +1 -0
- package/dist/logging/index.d.ts +3 -0
- package/dist/logging/index.js +3 -0
- package/dist/logging-CGHjOVLM.js +24 -0
- package/dist/logging-CGHjOVLM.js.map +1 -0
- package/dist/policy-CvS6AvvD.js +20 -0
- package/dist/policy-CvS6AvvD.js.map +1 -0
- package/dist/problem-details/index.d.ts +4 -0
- package/dist/problem-details/index.js +3 -0
- package/dist/problem-details-CuRsLy3Q.js +37 -0
- package/dist/problem-details-CuRsLy3Q.js.map +1 -0
- package/dist/silent-CWpHE65X.js +652 -0
- package/dist/silent-CWpHE65X.js.map +1 -0
- package/dist/testing/index.d.ts +44 -0
- package/dist/testing/index.js +2 -0
- package/dist/types-Cx6NNILW.d.ts +44 -0
- package/dist/types-DCSMb1cp.d.ts +195 -0
- package/dist/types-DXboaWOT.d.ts +29 -0
- package/dist/vite/index.d.ts +79 -0
- package/dist/vite/index.js +417 -0
- package/dist/vite/index.js.map +1 -0
- package/package.json +137 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
//#region src/lib/problem-details/status.ts
|
|
2
|
+
/**
|
|
3
|
+
* @file Standard HTTP reason phrases and URL-safe slugs, keyed by status code.
|
|
4
|
+
*/
|
|
5
|
+
const STATUS_PHRASES = {
|
|
6
|
+
400: "Bad Request",
|
|
7
|
+
401: "Unauthorized",
|
|
8
|
+
402: "Payment Required",
|
|
9
|
+
403: "Forbidden",
|
|
10
|
+
404: "Not Found",
|
|
11
|
+
405: "Method Not Allowed",
|
|
12
|
+
406: "Not Acceptable",
|
|
13
|
+
407: "Proxy Authentication Required",
|
|
14
|
+
408: "Request Timeout",
|
|
15
|
+
409: "Conflict",
|
|
16
|
+
410: "Gone",
|
|
17
|
+
411: "Length Required",
|
|
18
|
+
412: "Precondition Failed",
|
|
19
|
+
413: "Content Too Large",
|
|
20
|
+
414: "URI Too Long",
|
|
21
|
+
415: "Unsupported Media Type",
|
|
22
|
+
416: "Range Not Satisfiable",
|
|
23
|
+
417: "Expectation Failed",
|
|
24
|
+
418: "I'm a Teapot",
|
|
25
|
+
421: "Misdirected Request",
|
|
26
|
+
422: "Unprocessable Content",
|
|
27
|
+
423: "Locked",
|
|
28
|
+
424: "Failed Dependency",
|
|
29
|
+
425: "Too Early",
|
|
30
|
+
426: "Upgrade Required",
|
|
31
|
+
428: "Precondition Required",
|
|
32
|
+
429: "Too Many Requests",
|
|
33
|
+
431: "Request Header Fields Too Large",
|
|
34
|
+
451: "Unavailable For Legal Reasons",
|
|
35
|
+
500: "Internal Server Error",
|
|
36
|
+
501: "Not Implemented",
|
|
37
|
+
502: "Bad Gateway",
|
|
38
|
+
503: "Service Unavailable",
|
|
39
|
+
504: "Gateway Timeout",
|
|
40
|
+
505: "HTTP Version Not Supported",
|
|
41
|
+
506: "Variant Also Negotiates",
|
|
42
|
+
507: "Insufficient Storage",
|
|
43
|
+
508: "Loop Detected",
|
|
44
|
+
510: "Not Extended",
|
|
45
|
+
511: "Network Authentication Required"
|
|
46
|
+
};
|
|
47
|
+
const STATUS_SLUGS = Object.fromEntries(Object.entries(STATUS_PHRASES).map(([code, phrase]) => [Number(code), phrase.toLowerCase().replace(/'/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")]));
|
|
48
|
+
/**
|
|
49
|
+
* Look up the standard HTTP reason phrase for a status code (e.g. `404` → `"Not Found"`).
|
|
50
|
+
*
|
|
51
|
+
* @param status - The HTTP status code.
|
|
52
|
+
* @returns The reason phrase, or `undefined` if the status code is not recognized.
|
|
53
|
+
*/
|
|
54
|
+
function statusToPhrase(status) {
|
|
55
|
+
return STATUS_PHRASES[status];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Look up a URL-safe slug for a status code (e.g. `404` → `"not-found"`). Useful when building a
|
|
59
|
+
* `type` URI from a status code.
|
|
60
|
+
*
|
|
61
|
+
* @param status - The HTTP status code.
|
|
62
|
+
* @returns The slug, or `undefined` if the status code is not recognized.
|
|
63
|
+
*/
|
|
64
|
+
function statusToSlug(status) {
|
|
65
|
+
return STATUS_SLUGS[status];
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/lib/problem-details/utils.ts
|
|
69
|
+
/**
|
|
70
|
+
* @file Helper functions for sanitizing, normalizing, and serializing RFC 9457 Problem Details
|
|
71
|
+
* objects into `application/problem+json` responses.
|
|
72
|
+
*/
|
|
73
|
+
/** RFC 9457 media type: `application/problem+json; charset=utf-8`. */
|
|
74
|
+
const PROBLEM_JSON_CONTENT_TYPE = "application/problem+json; charset=utf-8";
|
|
75
|
+
const DANGEROUS_KEYS = /* @__PURE__ */ new Set([
|
|
76
|
+
"__proto__",
|
|
77
|
+
"constructor",
|
|
78
|
+
"prototype"
|
|
79
|
+
]);
|
|
80
|
+
/**
|
|
81
|
+
* Strip keys that could cause prototype pollution in downstream consumers.
|
|
82
|
+
*
|
|
83
|
+
* @param extensions - The extension members to sanitize, or `undefined`.
|
|
84
|
+
* @returns The sanitized extensions, or the original reference when nothing was stripped.
|
|
85
|
+
*/
|
|
86
|
+
function sanitizeExtensions(extensions) {
|
|
87
|
+
if (!extensions) return extensions;
|
|
88
|
+
let filtered;
|
|
89
|
+
for (const key of Object.keys(extensions)) if (DANGEROUS_KEYS.has(key)) {
|
|
90
|
+
filtered ??= { ...extensions };
|
|
91
|
+
delete filtered[key];
|
|
92
|
+
}
|
|
93
|
+
return filtered ?? extensions;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Clamp an HTTP status to the 200-599 integer range.
|
|
97
|
+
*
|
|
98
|
+
* @param status - The status to clamp.
|
|
99
|
+
* @returns `status` unchanged when it's an integer in `[200, 599]`; otherwise `500`.
|
|
100
|
+
*/
|
|
101
|
+
function clampHttpStatus(status) {
|
|
102
|
+
return Number.isInteger(status) && status >= 200 && status <= 599 ? status : 500;
|
|
103
|
+
}
|
|
104
|
+
const FALLBACK_BODY = JSON.stringify({
|
|
105
|
+
type: "about:blank",
|
|
106
|
+
status: 500,
|
|
107
|
+
title: "Internal Server Error"
|
|
108
|
+
});
|
|
109
|
+
/**
|
|
110
|
+
* Normalize a {@link ProblemDetailsInput} to a {@link ProblemDetails}, applying defaults for
|
|
111
|
+
* `type` and `title`.
|
|
112
|
+
*
|
|
113
|
+
* @param input - The input to normalize.
|
|
114
|
+
* @returns The normalized problem details object.
|
|
115
|
+
*/
|
|
116
|
+
function normalizeProblemDetails(input) {
|
|
117
|
+
return {
|
|
118
|
+
type: input.type ?? "about:blank",
|
|
119
|
+
status: input.status,
|
|
120
|
+
title: input.title ?? statusToPhrase(input.status) ?? "Unknown Error",
|
|
121
|
+
detail: input.detail,
|
|
122
|
+
instance: input.instance,
|
|
123
|
+
extensions: input.extensions
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Build an RFC 9457 `application/problem+json` {@link Response} from a {@link ProblemDetails}
|
|
128
|
+
* object.
|
|
129
|
+
*
|
|
130
|
+
* @param pd - The problem details to serialize.
|
|
131
|
+
* @returns The resulting `Response`.
|
|
132
|
+
*/
|
|
133
|
+
function buildProblemResponse(pd) {
|
|
134
|
+
const { extensions, ...standard } = pd;
|
|
135
|
+
const { json, fallback } = safeStringify({
|
|
136
|
+
...sanitizeExtensions(extensions),
|
|
137
|
+
...standard
|
|
138
|
+
});
|
|
139
|
+
return new Response(json, {
|
|
140
|
+
status: fallback ? 500 : clampHttpStatus(pd.status),
|
|
141
|
+
headers: { "Content-Type": PROBLEM_JSON_CONTENT_TYPE }
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* `JSON.stringify` with a fallback for non-serializable values (circular references, `BigInt`).
|
|
146
|
+
*
|
|
147
|
+
* @param body - The value to stringify.
|
|
148
|
+
* @returns The JSON string and whether the fallback body was used.
|
|
149
|
+
*/
|
|
150
|
+
function safeStringify(body) {
|
|
151
|
+
try {
|
|
152
|
+
return {
|
|
153
|
+
json: JSON.stringify(body),
|
|
154
|
+
fallback: false
|
|
155
|
+
};
|
|
156
|
+
} catch {
|
|
157
|
+
return {
|
|
158
|
+
json: FALLBACK_BODY,
|
|
159
|
+
fallback: true
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
//#endregion
|
|
164
|
+
//#region src/lib/problem-details/error.ts
|
|
165
|
+
/**
|
|
166
|
+
* Error class for RFC 9457 Problem Details.
|
|
167
|
+
* Thrown by {@link problemDetails} and caught by the Hono error handler (`@adrianhall/cloudflare-toolkit/hono`)
|
|
168
|
+
* to produce `application/problem+json` responses.
|
|
169
|
+
*/
|
|
170
|
+
var ProblemDetailsError = class extends Error {
|
|
171
|
+
/** The normalized RFC 9457 Problem Details object. */
|
|
172
|
+
problemDetails;
|
|
173
|
+
/**
|
|
174
|
+
* Create a new {@link ProblemDetailsError}.
|
|
175
|
+
*
|
|
176
|
+
* @param input - Missing `type` defaults to `"about:blank"`; missing `title` is derived from
|
|
177
|
+
* the HTTP status code.
|
|
178
|
+
*/
|
|
179
|
+
constructor(input) {
|
|
180
|
+
const pd = normalizeProblemDetails(input);
|
|
181
|
+
super(pd.detail ?? pd.title);
|
|
182
|
+
this.name = "ProblemDetailsError";
|
|
183
|
+
this.problemDetails = pd;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Build a standalone `application/problem+json` {@link Response} without any Hono handler
|
|
187
|
+
* middleware.
|
|
188
|
+
*
|
|
189
|
+
* @returns The resulting `Response`.
|
|
190
|
+
*/
|
|
191
|
+
getResponse() {
|
|
192
|
+
return buildProblemResponse(this.problemDetails);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
//#endregion
|
|
196
|
+
//#region src/lib/problem-details/factory.ts
|
|
197
|
+
/**
|
|
198
|
+
* @file The `problemDetails()` factory function for creating a {@link ProblemDetailsError}.
|
|
199
|
+
*/
|
|
200
|
+
/**
|
|
201
|
+
* Create a {@link ProblemDetailsError} from the given input.
|
|
202
|
+
* Missing `type` defaults to `"about:blank"`; missing `title` is derived from the status code.
|
|
203
|
+
*
|
|
204
|
+
* @param input - The problem details input.
|
|
205
|
+
* @returns A {@link ProblemDetailsError} ready to `throw`.
|
|
206
|
+
* @example
|
|
207
|
+
* ```ts
|
|
208
|
+
* throw problemDetails({
|
|
209
|
+
* status: 404,
|
|
210
|
+
* detail: `Order ${orderId} does not exist`,
|
|
211
|
+
* });
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
function problemDetails(input) {
|
|
215
|
+
return new ProblemDetailsError(input);
|
|
216
|
+
}
|
|
217
|
+
//#endregion
|
|
218
|
+
export { statusToPhrase as a, normalizeProblemDetails as i, ProblemDetailsError as n, statusToSlug as o, buildProblemResponse as r, problemDetails as t };
|
|
219
|
+
|
|
220
|
+
//# sourceMappingURL=factory-BI5gVL_P.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory-BI5gVL_P.js","names":[],"sources":["../src/lib/problem-details/status.ts","../src/lib/problem-details/utils.ts","../src/lib/problem-details/error.ts","../src/lib/problem-details/factory.ts"],"sourcesContent":["/**\n * @file Standard HTTP reason phrases and URL-safe slugs, keyed by status code.\n */\nconst STATUS_PHRASES: Record<number, string> = {\n 400: \"Bad Request\",\n 401: \"Unauthorized\",\n 402: \"Payment Required\",\n 403: \"Forbidden\",\n 404: \"Not Found\",\n 405: \"Method Not Allowed\",\n 406: \"Not Acceptable\",\n 407: \"Proxy Authentication Required\",\n 408: \"Request Timeout\",\n 409: \"Conflict\",\n 410: \"Gone\",\n 411: \"Length Required\",\n 412: \"Precondition Failed\",\n 413: \"Content Too Large\",\n 414: \"URI Too Long\",\n 415: \"Unsupported Media Type\",\n 416: \"Range Not Satisfiable\",\n 417: \"Expectation Failed\",\n 418: \"I'm a Teapot\",\n 421: \"Misdirected Request\",\n 422: \"Unprocessable Content\",\n 423: \"Locked\",\n 424: \"Failed Dependency\",\n 425: \"Too Early\",\n 426: \"Upgrade Required\",\n 428: \"Precondition Required\",\n 429: \"Too Many Requests\",\n 431: \"Request Header Fields Too Large\",\n 451: \"Unavailable For Legal Reasons\",\n 500: \"Internal Server Error\",\n 501: \"Not Implemented\",\n 502: \"Bad Gateway\",\n 503: \"Service Unavailable\",\n 504: \"Gateway Timeout\",\n 505: \"HTTP Version Not Supported\",\n 506: \"Variant Also Negotiates\",\n 507: \"Insufficient Storage\",\n 508: \"Loop Detected\",\n 510: \"Not Extended\",\n 511: \"Network Authentication Required\"\n};\n\nconst STATUS_SLUGS: Record<number, string> = Object.fromEntries(\n Object.entries(STATUS_PHRASES).map(([code, phrase]) => [\n Number(code),\n phrase\n .toLowerCase()\n .replace(/'/g, \"\")\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\")\n ])\n);\n\n/**\n * Look up the standard HTTP reason phrase for a status code (e.g. `404` → `\"Not Found\"`).\n *\n * @param status - The HTTP status code.\n * @returns The reason phrase, or `undefined` if the status code is not recognized.\n */\nexport function statusToPhrase(status: number): string | undefined {\n return STATUS_PHRASES[status];\n}\n\n/**\n * Look up a URL-safe slug for a status code (e.g. `404` → `\"not-found\"`). Useful when building a\n * `type` URI from a status code.\n *\n * @param status - The HTTP status code.\n * @returns The slug, or `undefined` if the status code is not recognized.\n */\nexport function statusToSlug(status: number): string | undefined {\n return STATUS_SLUGS[status];\n}\n","/**\n * @file Helper functions for sanitizing, normalizing, and serializing RFC 9457 Problem Details\n * objects into `application/problem+json` responses.\n */\nimport { statusToPhrase } from \"./status.js\";\nimport type { ProblemDetails, ProblemDetailsInput } from \"./types.js\";\n\n/** RFC 9457 media type: `application/problem+json; charset=utf-8`. */\nexport const PROBLEM_JSON_CONTENT_TYPE = \"application/problem+json; charset=utf-8\";\n\nconst DANGEROUS_KEYS = new Set([\"__proto__\", \"constructor\", \"prototype\"]);\n\n/**\n * Strip keys that could cause prototype pollution in downstream consumers.\n *\n * @param extensions - The extension members to sanitize, or `undefined`.\n * @returns The sanitized extensions, or the original reference when nothing was stripped.\n */\nexport function sanitizeExtensions(\n extensions: Record<string, unknown> | undefined\n): Record<string, unknown> | undefined {\n if (!extensions) return extensions;\n let filtered: Record<string, unknown> | undefined;\n for (const key of Object.keys(extensions)) {\n if (DANGEROUS_KEYS.has(key)) {\n filtered ??= { ...extensions };\n delete filtered[key];\n }\n }\n return filtered ?? extensions;\n}\n\n/**\n * Clamp an HTTP status to the 200-599 integer range.\n *\n * @param status - The status to clamp.\n * @returns `status` unchanged when it's an integer in `[200, 599]`; otherwise `500`.\n */\nexport function clampHttpStatus(status: number): number {\n return Number.isInteger(status) && status >= 200 && status <= 599 ? status : 500;\n}\n\nconst FALLBACK_BODY = JSON.stringify({\n type: \"about:blank\",\n status: 500,\n title: \"Internal Server Error\"\n});\n\n/**\n * Normalize a {@link ProblemDetailsInput} to a {@link ProblemDetails}, applying defaults for\n * `type` and `title`.\n *\n * @param input - The input to normalize.\n * @returns The normalized problem details object.\n */\nexport function normalizeProblemDetails<T extends Record<string, unknown>>(\n input: ProblemDetailsInput<T>\n): ProblemDetails<T> {\n return {\n type: input.type ?? \"about:blank\",\n status: input.status,\n title: input.title ?? statusToPhrase(input.status) ?? \"Unknown Error\",\n detail: input.detail,\n instance: input.instance,\n extensions: input.extensions\n };\n}\n\n/**\n * Build an RFC 9457 `application/problem+json` {@link Response} from a {@link ProblemDetails}\n * object.\n *\n * @param pd - The problem details to serialize.\n * @returns The resulting `Response`.\n */\nexport function buildProblemResponse(pd: ProblemDetails): Response {\n const { extensions, ...standard } = pd;\n const body = { ...sanitizeExtensions(extensions), ...standard };\n const { json, fallback } = safeStringify(body);\n return new Response(json, {\n status: fallback ? 500 : clampHttpStatus(pd.status),\n headers: { \"Content-Type\": PROBLEM_JSON_CONTENT_TYPE }\n });\n}\n\n/**\n * `JSON.stringify` with a fallback for non-serializable values (circular references, `BigInt`).\n *\n * @param body - The value to stringify.\n * @returns The JSON string and whether the fallback body was used.\n */\nexport function safeStringify(body: unknown): { json: string; fallback: boolean } {\n try {\n return { json: JSON.stringify(body), fallback: false };\n } catch {\n return { json: FALLBACK_BODY, fallback: true };\n }\n}\n","/**\n * @file The RFC 9457 Problem Details error class.\n */\nimport type { ProblemDetails, ProblemDetailsInput } from \"./types.js\";\nimport { buildProblemResponse, normalizeProblemDetails } from \"./utils.js\";\n\n/**\n * Error class for RFC 9457 Problem Details.\n * Thrown by {@link problemDetails} and caught by the Hono error handler (`@adrianhall/cloudflare-toolkit/hono`)\n * to produce `application/problem+json` responses.\n */\nexport class ProblemDetailsError extends Error {\n /** The normalized RFC 9457 Problem Details object. */\n readonly problemDetails: ProblemDetails;\n\n /**\n * Create a new {@link ProblemDetailsError}.\n *\n * @param input - Missing `type` defaults to `\"about:blank\"`; missing `title` is derived from\n * the HTTP status code.\n */\n constructor(input: ProblemDetailsInput) {\n const pd = normalizeProblemDetails(input);\n super(pd.detail ?? pd.title);\n this.name = \"ProblemDetailsError\";\n this.problemDetails = pd;\n }\n\n /**\n * Build a standalone `application/problem+json` {@link Response} without any Hono handler\n * middleware.\n *\n * @returns The resulting `Response`.\n */\n getResponse(): Response {\n return buildProblemResponse(this.problemDetails);\n }\n}\n","/**\n * @file The `problemDetails()` factory function for creating a {@link ProblemDetailsError}.\n */\nimport { ProblemDetailsError } from \"./error.js\";\nimport type { ProblemDetailsInput } from \"./types.js\";\n\n/**\n * Create a {@link ProblemDetailsError} from the given input.\n * Missing `type` defaults to `\"about:blank\"`; missing `title` is derived from the status code.\n *\n * @param input - The problem details input.\n * @returns A {@link ProblemDetailsError} ready to `throw`.\n * @example\n * ```ts\n * throw problemDetails({\n * status: 404,\n * detail: `Order ${orderId} does not exist`,\n * });\n * ```\n */\nexport function problemDetails<T extends Record<string, unknown>>(\n input: ProblemDetailsInput<T>\n): ProblemDetailsError {\n return new ProblemDetailsError(input);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { t as problemDetails } from "./factory-BI5gVL_P.js";
|
|
2
|
+
//#region src/lib/errors/generators.ts
|
|
3
|
+
/**
|
|
4
|
+
* @file One HTTP error generator per supported status code. Every generator has the signature
|
|
5
|
+
* `(input?: Omit<ProblemDetailsInput, "status">) => ProblemDetailsError` — each fixes its own
|
|
6
|
+
* `status` and forwards `detail`/`type`/`instance`/`extensions` untouched to `problemDetails()`;
|
|
7
|
+
* `title` auto-derives from `status` unless the caller explicitly overrides it.
|
|
8
|
+
*
|
|
9
|
+
* These are plain functions, not framework-specific — throwing one inside a plain function, a
|
|
10
|
+
* Durable Object method, or a Hono handler all work identically. Pairing one with
|
|
11
|
+
* `problemDetailsErrorHandler` (`@adrianhall/cloudflare-toolkit/hono`) turns the throw into an
|
|
12
|
+
* HTTP response.
|
|
13
|
+
*
|
|
14
|
+
* `429 Too Many Requests` is intentionally not included (a platform concern, not an
|
|
15
|
+
* application-level one). `304 Not Modified`, `409 Conflict`, and `412 Precondition Failed` are
|
|
16
|
+
* also not included.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Create a `400 Bad Request` {@link ProblemDetailsError}.
|
|
20
|
+
*
|
|
21
|
+
* @param input - Optional problem details fields (`detail`, `type`, `instance`, `extensions`).
|
|
22
|
+
* @returns A `400`-status {@link ProblemDetailsError} ready to `throw`.
|
|
23
|
+
*/
|
|
24
|
+
function badRequest(input) {
|
|
25
|
+
return problemDetails({
|
|
26
|
+
...input,
|
|
27
|
+
status: 400
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Create a `401 Unauthorized` {@link ProblemDetailsError}.
|
|
32
|
+
*
|
|
33
|
+
* @param input - Optional problem details fields (`detail`, `type`, `instance`, `extensions`).
|
|
34
|
+
* @returns A `401`-status {@link ProblemDetailsError} ready to `throw`.
|
|
35
|
+
*/
|
|
36
|
+
function unauthorized(input) {
|
|
37
|
+
return problemDetails({
|
|
38
|
+
...input,
|
|
39
|
+
status: 401
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Create a `403 Forbidden` {@link ProblemDetailsError}.
|
|
44
|
+
*
|
|
45
|
+
* @param input - Optional problem details fields (`detail`, `type`, `instance`, `extensions`).
|
|
46
|
+
* @returns A `403`-status {@link ProblemDetailsError} ready to `throw`.
|
|
47
|
+
*/
|
|
48
|
+
function forbidden(input) {
|
|
49
|
+
return problemDetails({
|
|
50
|
+
...input,
|
|
51
|
+
status: 403
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Create a `404 Not Found` {@link ProblemDetailsError}.
|
|
56
|
+
*
|
|
57
|
+
* @param input - Optional problem details fields (`detail`, `type`, `instance`, `extensions`).
|
|
58
|
+
* @returns A `404`-status {@link ProblemDetailsError} ready to `throw`.
|
|
59
|
+
*/
|
|
60
|
+
function notFound(input) {
|
|
61
|
+
return problemDetails({
|
|
62
|
+
...input,
|
|
63
|
+
status: 404
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Create a `405 Method Not Allowed` {@link ProblemDetailsError}.
|
|
68
|
+
*
|
|
69
|
+
* @param input - Optional problem details fields (`detail`, `type`, `instance`, `extensions`).
|
|
70
|
+
* @returns A `405`-status {@link ProblemDetailsError} ready to `throw`.
|
|
71
|
+
*/
|
|
72
|
+
function methodNotAllowed(input) {
|
|
73
|
+
return problemDetails({
|
|
74
|
+
...input,
|
|
75
|
+
status: 405
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Create a `410 Gone` {@link ProblemDetailsError}.
|
|
80
|
+
*
|
|
81
|
+
* @param input - Optional problem details fields (`detail`, `type`, `instance`, `extensions`).
|
|
82
|
+
* @returns A `410`-status {@link ProblemDetailsError} ready to `throw`.
|
|
83
|
+
*/
|
|
84
|
+
function gone(input) {
|
|
85
|
+
return problemDetails({
|
|
86
|
+
...input,
|
|
87
|
+
status: 410
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Create a `413 Content Too Large` {@link ProblemDetailsError}.
|
|
92
|
+
*
|
|
93
|
+
* @param input - Optional problem details fields (`detail`, `type`, `instance`, `extensions`).
|
|
94
|
+
* @returns A `413`-status {@link ProblemDetailsError} ready to `throw`.
|
|
95
|
+
*/
|
|
96
|
+
function contentTooLarge(input) {
|
|
97
|
+
return problemDetails({
|
|
98
|
+
...input,
|
|
99
|
+
status: 413
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Create a `415 Unsupported Media Type` {@link ProblemDetailsError}.
|
|
104
|
+
*
|
|
105
|
+
* @param input - Optional problem details fields (`detail`, `type`, `instance`, `extensions`).
|
|
106
|
+
* @returns A `415`-status {@link ProblemDetailsError} ready to `throw`.
|
|
107
|
+
*/
|
|
108
|
+
function unsupportedMediaType(input) {
|
|
109
|
+
return problemDetails({
|
|
110
|
+
...input,
|
|
111
|
+
status: 415
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Create a `422 Unprocessable Content` {@link ProblemDetailsError}.
|
|
116
|
+
*
|
|
117
|
+
* @param input - Optional problem details fields (`detail`, `type`, `instance`, `extensions`).
|
|
118
|
+
* @returns A `422`-status {@link ProblemDetailsError} ready to `throw`.
|
|
119
|
+
*/
|
|
120
|
+
function unprocessableContent(input) {
|
|
121
|
+
return problemDetails({
|
|
122
|
+
...input,
|
|
123
|
+
status: 422
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Create a `500 Internal Server Error` {@link ProblemDetailsError}.
|
|
128
|
+
*
|
|
129
|
+
* @param input - Optional problem details fields (`detail`, `type`, `instance`, `extensions`).
|
|
130
|
+
* @returns A `500`-status {@link ProblemDetailsError} ready to `throw`.
|
|
131
|
+
*/
|
|
132
|
+
function internalServerError(input) {
|
|
133
|
+
return problemDetails({
|
|
134
|
+
...input,
|
|
135
|
+
status: 500
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Create a `501 Not Implemented` {@link ProblemDetailsError}.
|
|
140
|
+
*
|
|
141
|
+
* @param input - Optional problem details fields (`detail`, `type`, `instance`, `extensions`).
|
|
142
|
+
* @returns A `501`-status {@link ProblemDetailsError} ready to `throw`.
|
|
143
|
+
*/
|
|
144
|
+
function notImplemented(input) {
|
|
145
|
+
return problemDetails({
|
|
146
|
+
...input,
|
|
147
|
+
status: 501
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Create a `503 Service Unavailable` {@link ProblemDetailsError}.
|
|
152
|
+
*
|
|
153
|
+
* @param input - Optional problem details fields (`detail`, `type`, `instance`, `extensions`).
|
|
154
|
+
* @returns A `503`-status {@link ProblemDetailsError} ready to `throw`.
|
|
155
|
+
*/
|
|
156
|
+
function serviceUnavailable(input) {
|
|
157
|
+
return problemDetails({
|
|
158
|
+
...input,
|
|
159
|
+
status: 503
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
//#endregion
|
|
163
|
+
export { internalServerError as a, notImplemented as c, unprocessableContent as d, unsupportedMediaType as f, gone as i, serviceUnavailable as l, contentTooLarge as n, methodNotAllowed as o, forbidden as r, notFound as s, badRequest as t, unauthorized as u };
|
|
164
|
+
|
|
165
|
+
//# sourceMappingURL=generators-D8WWEHa1.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generators-D8WWEHa1.js","names":[],"sources":["../src/lib/errors/generators.ts"],"sourcesContent":["/**\n * @file One HTTP error generator per supported status code. Every generator has the signature\n * `(input?: Omit<ProblemDetailsInput, \"status\">) => ProblemDetailsError` — each fixes its own\n * `status` and forwards `detail`/`type`/`instance`/`extensions` untouched to `problemDetails()`;\n * `title` auto-derives from `status` unless the caller explicitly overrides it.\n *\n * These are plain functions, not framework-specific — throwing one inside a plain function, a\n * Durable Object method, or a Hono handler all work identically. Pairing one with\n * `problemDetailsErrorHandler` (`@adrianhall/cloudflare-toolkit/hono`) turns the throw into an\n * HTTP response.\n *\n * `429 Too Many Requests` is intentionally not included (a platform concern, not an\n * application-level one). `304 Not Modified`, `409 Conflict`, and `412 Precondition Failed` are\n * also not included.\n */\nimport { problemDetails } from \"../problem-details/factory.js\";\nimport type { ProblemDetailsError } from \"../problem-details/error.js\";\nimport type { ProblemDetailsInput } from \"../problem-details/types.js\";\n\n/**\n * Input shared by every HTTP error generator: a {@link ProblemDetailsInput} without `status`,\n * since each generator supplies its own fixed status code.\n */\nexport type HttpErrorInput<T extends Record<string, unknown> = Record<string, unknown>> = Omit<\n ProblemDetailsInput<T>,\n \"status\"\n>;\n\n/**\n * Create a `400 Bad Request` {@link ProblemDetailsError}.\n *\n * @param input - Optional problem details fields (`detail`, `type`, `instance`, `extensions`).\n * @returns A `400`-status {@link ProblemDetailsError} ready to `throw`.\n */\nexport function badRequest(input?: HttpErrorInput): ProblemDetailsError {\n return problemDetails({ ...input, status: 400 });\n}\n\n/**\n * Create a `401 Unauthorized` {@link ProblemDetailsError}.\n *\n * @param input - Optional problem details fields (`detail`, `type`, `instance`, `extensions`).\n * @returns A `401`-status {@link ProblemDetailsError} ready to `throw`.\n */\nexport function unauthorized(input?: HttpErrorInput): ProblemDetailsError {\n return problemDetails({ ...input, status: 401 });\n}\n\n/**\n * Create a `403 Forbidden` {@link ProblemDetailsError}.\n *\n * @param input - Optional problem details fields (`detail`, `type`, `instance`, `extensions`).\n * @returns A `403`-status {@link ProblemDetailsError} ready to `throw`.\n */\nexport function forbidden(input?: HttpErrorInput): ProblemDetailsError {\n return problemDetails({ ...input, status: 403 });\n}\n\n/**\n * Create a `404 Not Found` {@link ProblemDetailsError}.\n *\n * @param input - Optional problem details fields (`detail`, `type`, `instance`, `extensions`).\n * @returns A `404`-status {@link ProblemDetailsError} ready to `throw`.\n */\nexport function notFound(input?: HttpErrorInput): ProblemDetailsError {\n return problemDetails({ ...input, status: 404 });\n}\n\n/**\n * Create a `405 Method Not Allowed` {@link ProblemDetailsError}.\n *\n * @param input - Optional problem details fields (`detail`, `type`, `instance`, `extensions`).\n * @returns A `405`-status {@link ProblemDetailsError} ready to `throw`.\n */\nexport function methodNotAllowed(input?: HttpErrorInput): ProblemDetailsError {\n return problemDetails({ ...input, status: 405 });\n}\n\n/**\n * Create a `410 Gone` {@link ProblemDetailsError}.\n *\n * @param input - Optional problem details fields (`detail`, `type`, `instance`, `extensions`).\n * @returns A `410`-status {@link ProblemDetailsError} ready to `throw`.\n */\nexport function gone(input?: HttpErrorInput): ProblemDetailsError {\n return problemDetails({ ...input, status: 410 });\n}\n\n/**\n * Create a `413 Content Too Large` {@link ProblemDetailsError}.\n *\n * @param input - Optional problem details fields (`detail`, `type`, `instance`, `extensions`).\n * @returns A `413`-status {@link ProblemDetailsError} ready to `throw`.\n */\nexport function contentTooLarge(input?: HttpErrorInput): ProblemDetailsError {\n return problemDetails({ ...input, status: 413 });\n}\n\n/**\n * Create a `415 Unsupported Media Type` {@link ProblemDetailsError}.\n *\n * @param input - Optional problem details fields (`detail`, `type`, `instance`, `extensions`).\n * @returns A `415`-status {@link ProblemDetailsError} ready to `throw`.\n */\nexport function unsupportedMediaType(input?: HttpErrorInput): ProblemDetailsError {\n return problemDetails({ ...input, status: 415 });\n}\n\n/**\n * Create a `422 Unprocessable Content` {@link ProblemDetailsError}.\n *\n * @param input - Optional problem details fields (`detail`, `type`, `instance`, `extensions`).\n * @returns A `422`-status {@link ProblemDetailsError} ready to `throw`.\n */\nexport function unprocessableContent(input?: HttpErrorInput): ProblemDetailsError {\n return problemDetails({ ...input, status: 422 });\n}\n\n/**\n * Create a `500 Internal Server Error` {@link ProblemDetailsError}.\n *\n * @param input - Optional problem details fields (`detail`, `type`, `instance`, `extensions`).\n * @returns A `500`-status {@link ProblemDetailsError} ready to `throw`.\n */\nexport function internalServerError(input?: HttpErrorInput): ProblemDetailsError {\n return problemDetails({ ...input, status: 500 });\n}\n\n/**\n * Create a `501 Not Implemented` {@link ProblemDetailsError}.\n *\n * @param input - Optional problem details fields (`detail`, `type`, `instance`, `extensions`).\n * @returns A `501`-status {@link ProblemDetailsError} ready to `throw`.\n */\nexport function notImplemented(input?: HttpErrorInput): ProblemDetailsError {\n return problemDetails({ ...input, status: 501 });\n}\n\n/**\n * Create a `503 Service Unavailable` {@link ProblemDetailsError}.\n *\n * @param input - Optional problem details fields (`detail`, `type`, `instance`, `extensions`).\n * @returns A `503`-status {@link ProblemDetailsError} ready to `throw`.\n */\nexport function serviceUnavailable(input?: HttpErrorInput): ProblemDetailsError {\n return problemDetails({ ...input, status: 503 });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAkCA,SAAgB,WAAW,OAA6C;CACtE,OAAO,eAAe;EAAE,GAAG;EAAO,QAAQ;CAAI,CAAC;AACjD;;;;;;;AAQA,SAAgB,aAAa,OAA6C;CACxE,OAAO,eAAe;EAAE,GAAG;EAAO,QAAQ;CAAI,CAAC;AACjD;;;;;;;AAQA,SAAgB,UAAU,OAA6C;CACrE,OAAO,eAAe;EAAE,GAAG;EAAO,QAAQ;CAAI,CAAC;AACjD;;;;;;;AAQA,SAAgB,SAAS,OAA6C;CACpE,OAAO,eAAe;EAAE,GAAG;EAAO,QAAQ;CAAI,CAAC;AACjD;;;;;;;AAQA,SAAgB,iBAAiB,OAA6C;CAC5E,OAAO,eAAe;EAAE,GAAG;EAAO,QAAQ;CAAI,CAAC;AACjD;;;;;;;AAQA,SAAgB,KAAK,OAA6C;CAChE,OAAO,eAAe;EAAE,GAAG;EAAO,QAAQ;CAAI,CAAC;AACjD;;;;;;;AAQA,SAAgB,gBAAgB,OAA6C;CAC3E,OAAO,eAAe;EAAE,GAAG;EAAO,QAAQ;CAAI,CAAC;AACjD;;;;;;;AAQA,SAAgB,qBAAqB,OAA6C;CAChF,OAAO,eAAe;EAAE,GAAG;EAAO,QAAQ;CAAI,CAAC;AACjD;;;;;;;AAQA,SAAgB,qBAAqB,OAA6C;CAChF,OAAO,eAAe;EAAE,GAAG;EAAO,QAAQ;CAAI,CAAC;AACjD;;;;;;;AAQA,SAAgB,oBAAoB,OAA6C;CAC/E,OAAO,eAAe;EAAE,GAAG;EAAO,QAAQ;CAAI,CAAC;AACjD;;;;;;;AAQA,SAAgB,eAAe,OAA6C;CAC1E,OAAO,eAAe;EAAE,GAAG;EAAO,QAAQ;CAAI,CAAC;AACjD;;;;;;;AAQA,SAAgB,mBAAmB,OAA6C;CAC9E,OAAO,eAAe;EAAE,GAAG;EAAO,QAAQ;CAAI,CAAC;AACjD"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { n as InvalidShapeError, t as NullError } from "./errors-Ciipq_zr.js";
|
|
2
|
+
//#region src/lib/guards/guards.ts
|
|
3
|
+
/**
|
|
4
|
+
* @file Testable defensive guards — `throwIfNull`, `valueOrDefault`, `sqlCount` — that let call
|
|
5
|
+
* sites replace inline, ad-hoc defensive branches with a single, individually-tested helper.
|
|
6
|
+
*
|
|
7
|
+
* Depends only on `errors` (for `NullError` and `InvalidShapeError`) — never the reverse.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Assert that `value` is neither `null` nor `undefined`. A genuine TypeScript assertion function:
|
|
11
|
+
* once called, TypeScript narrows `value` to `NonNullable<T>` for the rest of the calling scope,
|
|
12
|
+
* so callers get type narrowing for free instead of needing a separate `if`/cast.
|
|
13
|
+
*
|
|
14
|
+
* @param value - The value to check.
|
|
15
|
+
* @param message - Human-readable explanation of what was unexpectedly null/undefined. Forwarded
|
|
16
|
+
* as-is to the thrown {@link NullError}.
|
|
17
|
+
* @throws {NullError} If `value` is `null` or `undefined`.
|
|
18
|
+
*/
|
|
19
|
+
function throwIfNull(value, message) {
|
|
20
|
+
if (value === null || value === void 0) throw new NullError(message);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Return `value` unless it is `null`/`undefined`, in which case return `defaultValue` instead.
|
|
24
|
+
* Literally `value ?? defaultValue` — exists purely so lint rules can flag _ad hoc_ `??`
|
|
25
|
+
* fallbacks used defensively while allowing this one blessed, individually-tested helper.
|
|
26
|
+
*
|
|
27
|
+
* @param value - The value to return if defined.
|
|
28
|
+
* @param defaultValue - The fallback returned when `value` is `null`/`undefined`.
|
|
29
|
+
* @returns `value`, or `defaultValue` when `value` is `null`/`undefined`.
|
|
30
|
+
*/
|
|
31
|
+
function valueOrDefault(value, defaultValue) {
|
|
32
|
+
return value ?? defaultValue;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Extract a numeric count from a D1 `.first()` result for the `SELECT COUNT(*) AS count FROM t`
|
|
36
|
+
* pattern. Validates that `row` is a non-null object with a numeric `countProperty`; throws
|
|
37
|
+
* {@link NullError} (via {@link throwIfNull}) if `row` itself is
|
|
38
|
+
* `null`/`undefined`, or {@link InvalidShapeError} if `row` is non-null but does not have the
|
|
39
|
+
* expected shape (not an object, or `countProperty` on it missing/not a number) — since the whole
|
|
40
|
+
* point of this guard is "this should never happen — if it does, that's a bug, not a 0".
|
|
41
|
+
*
|
|
42
|
+
* @param row - The value returned by D1's `.first()` — `null` when no rows match, otherwise
|
|
43
|
+
* whatever shape the query produced. Typed `unknown` because a D1 result is never trustworthy
|
|
44
|
+
* input.
|
|
45
|
+
* @param countProperty - The property on `row` holding the count. Defaults to `"count"`.
|
|
46
|
+
* @returns The numeric count read from `row[countProperty]`.
|
|
47
|
+
* @throws {NullError} If `row` is `null` or `undefined`.
|
|
48
|
+
* @throws {InvalidShapeError} If `row` is non-null but not an object, or `countProperty` on it is
|
|
49
|
+
* missing or not a number.
|
|
50
|
+
*/
|
|
51
|
+
function sqlCount(row, countProperty = "count") {
|
|
52
|
+
throwIfNull(row, "sqlCount: row is null or undefined (D1 .first() returned no rows)");
|
|
53
|
+
if (typeof row !== "object") throw new InvalidShapeError(`sqlCount: row is not an object (received ${typeof row})`);
|
|
54
|
+
const value = row[countProperty];
|
|
55
|
+
if (typeof value !== "number") throw new InvalidShapeError(`sqlCount: property "${countProperty}" is missing or not a number`);
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { throwIfNull as n, valueOrDefault as r, sqlCount as t };
|
|
60
|
+
|
|
61
|
+
//# sourceMappingURL=guards-6K1CVAr5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guards-6K1CVAr5.js","names":[],"sources":["../src/lib/guards/guards.ts"],"sourcesContent":["/**\n * @file Testable defensive guards — `throwIfNull`, `valueOrDefault`, `sqlCount` — that let call\n * sites replace inline, ad-hoc defensive branches with a single, individually-tested helper.\n *\n * Depends only on `errors` (for `NullError` and `InvalidShapeError`) — never the reverse.\n */\nimport { InvalidShapeError, NullError } from \"../errors/index.js\";\n\n/**\n * Assert that `value` is neither `null` nor `undefined`. A genuine TypeScript assertion function:\n * once called, TypeScript narrows `value` to `NonNullable<T>` for the rest of the calling scope,\n * so callers get type narrowing for free instead of needing a separate `if`/cast.\n *\n * @param value - The value to check.\n * @param message - Human-readable explanation of what was unexpectedly null/undefined. Forwarded\n * as-is to the thrown {@link NullError}.\n * @throws {NullError} If `value` is `null` or `undefined`.\n */\nexport function throwIfNull<T>(value: T, message: string): asserts value is NonNullable<T> {\n if (value === null || value === undefined) {\n throw new NullError(message);\n }\n}\n\n/**\n * Return `value` unless it is `null`/`undefined`, in which case return `defaultValue` instead.\n * Literally `value ?? defaultValue` — exists purely so lint rules can flag _ad hoc_ `??`\n * fallbacks used defensively while allowing this one blessed, individually-tested helper.\n *\n * @param value - The value to return if defined.\n * @param defaultValue - The fallback returned when `value` is `null`/`undefined`.\n * @returns `value`, or `defaultValue` when `value` is `null`/`undefined`.\n */\nexport function valueOrDefault<T>(value: T | null | undefined, defaultValue: T): T {\n return value ?? defaultValue;\n}\n\n/**\n * Extract a numeric count from a D1 `.first()` result for the `SELECT COUNT(*) AS count FROM t`\n * pattern. Validates that `row` is a non-null object with a numeric `countProperty`; throws\n * {@link NullError} (via {@link throwIfNull}) if `row` itself is\n * `null`/`undefined`, or {@link InvalidShapeError} if `row` is non-null but does not have the\n * expected shape (not an object, or `countProperty` on it missing/not a number) — since the whole\n * point of this guard is \"this should never happen — if it does, that's a bug, not a 0\".\n *\n * @param row - The value returned by D1's `.first()` — `null` when no rows match, otherwise\n * whatever shape the query produced. Typed `unknown` because a D1 result is never trustworthy\n * input.\n * @param countProperty - The property on `row` holding the count. Defaults to `\"count\"`.\n * @returns The numeric count read from `row[countProperty]`.\n * @throws {NullError} If `row` is `null` or `undefined`.\n * @throws {InvalidShapeError} If `row` is non-null but not an object, or `countProperty` on it is\n * missing or not a number.\n */\nexport function sqlCount(row: unknown, countProperty = \"count\"): number {\n throwIfNull(row, \"sqlCount: row is null or undefined (D1 .first() returned no rows)\");\n if (typeof row !== \"object\") {\n throw new InvalidShapeError(`sqlCount: row is not an object (received ${typeof row})`);\n }\n const value = (row as Record<string, unknown>)[countProperty];\n if (typeof value !== \"number\") {\n throw new InvalidShapeError(`sqlCount: property \"${countProperty}\" is missing or not a number`);\n }\n return value;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAkBA,SAAgB,YAAe,OAAU,SAAkD;CACzF,IAAI,UAAU,QAAQ,UAAU,KAAA,GAC9B,MAAM,IAAI,UAAU,OAAO;AAE/B;;;;;;;;;;AAWA,SAAgB,eAAkB,OAA6B,cAAoB;CACjF,OAAO,SAAS;AAClB;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,SAAS,KAAc,gBAAgB,SAAiB;CACtE,YAAY,KAAK,mEAAmE;CACpF,IAAI,OAAO,QAAQ,UACjB,MAAM,IAAI,kBAAkB,4CAA4C,OAAO,IAAI,EAAE;CAEvF,MAAM,QAAS,IAAgC;CAC/C,IAAI,OAAO,UAAU,UACnB,MAAM,IAAI,kBAAkB,uBAAuB,cAAc,6BAA6B;CAEhG,OAAO;AACT"}
|