better-ship 0.4.0 → 0.5.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/{application-BpSwCKJ5.js → application-DEerezCl.js} +2 -2
- package/dist/{application-BpSwCKJ5.js.map → application-DEerezCl.js.map} +1 -1
- package/dist/application.d.ts +1 -1
- package/dist/application.js +1 -1
- package/dist/better-auth/apple-client-secret.d.ts +21 -0
- package/dist/better-auth/apple-client-secret.d.ts.map +1 -0
- package/dist/better-auth/apple-client-secret.js +44 -0
- package/dist/better-auth/apple-client-secret.js.map +1 -0
- package/dist/better-auth/auth-client.d.ts +16 -0
- package/dist/better-auth/auth-client.d.ts.map +1 -0
- package/dist/better-auth/auth-client.js +24 -0
- package/dist/better-auth/auth-client.js.map +1 -0
- package/dist/better-auth/rate-limit.d.ts +34 -0
- package/dist/better-auth/rate-limit.d.ts.map +1 -0
- package/dist/better-auth/rate-limit.js +59 -0
- package/dist/better-auth/rate-limit.js.map +1 -0
- package/dist/better-auth/secondary-storage.d.ts +11 -0
- package/dist/better-auth/secondary-storage.d.ts.map +1 -0
- package/dist/better-auth/secondary-storage.js +18 -0
- package/dist/better-auth/secondary-storage.js.map +1 -0
- package/dist/better-auth/stripe.d.ts +9 -0
- package/dist/better-auth/stripe.d.ts.map +1 -0
- package/dist/better-auth/stripe.js +17 -0
- package/dist/better-auth/stripe.js.map +1 -0
- package/dist/better-auth.d.ts +100 -0
- package/dist/better-auth.d.ts.map +1 -0
- package/dist/better-auth.js +112 -0
- package/dist/better-auth.js.map +1 -0
- package/dist/cloudflare/auth-secondary-storage.d.ts +36 -0
- package/dist/cloudflare/auth-secondary-storage.d.ts.map +1 -0
- package/dist/cloudflare/auth-secondary-storage.js +98 -0
- package/dist/cloudflare/auth-secondary-storage.js.map +1 -0
- package/dist/cloudflare.d.ts +3 -3
- package/dist/cloudflare.js +3 -3
- package/dist/core-CnwWj4MY.js +620 -0
- package/dist/core-CnwWj4MY.js.map +1 -0
- package/dist/core.d.ts +2 -2
- package/dist/core.js +2 -2
- package/dist/index-B9Y1hVh6.d.ts +460 -0
- package/dist/index-B9Y1hVh6.d.ts.map +1 -0
- package/dist/{index-CkaFqbbV.d.ts → index-Bdyh3_xC.d.ts} +2 -2
- package/dist/{index-CkaFqbbV.d.ts.map → index-Bdyh3_xC.d.ts.map} +1 -1
- package/dist/postgres.d.ts +3 -3
- package/dist/postgres.js +3 -3
- package/dist/tanstack.d.ts +81 -1
- package/dist/tanstack.d.ts.map +1 -0
- package/dist/tanstack.js +214 -0
- package/dist/tanstack.js.map +1 -0
- package/dist/{unit-of-work-context-CILhzKWb.js → unit-of-work-context-C7vz5OYS.js} +2 -2
- package/dist/{unit-of-work-context-CILhzKWb.js.map → unit-of-work-context-C7vz5OYS.js.map} +1 -1
- package/dist/{unit-of-work-context-DOkIB7HK.d.ts → unit-of-work-context-ml9L9cyY.d.ts} +2 -2
- package/dist/{unit-of-work-context-DOkIB7HK.d.ts.map → unit-of-work-context-ml9L9cyY.d.ts.map} +1 -1
- package/package.json +57 -12
- package/src/core/authentication.error.ts +11 -0
- package/src/core/error-table.ts +188 -0
- package/src/core/index.ts +31 -0
- package/src/core/internal.errors.ts +21 -0
- package/src/core/logger.ts +40 -19
- package/src/core/not-authorized.error.ts +11 -0
- package/src/core/problem-details.ts +57 -0
- package/src/core/rate-limit.error.ts +12 -0
- package/src/core/request.errors.ts +86 -0
- package/src/core/stale-session.error.ts +11 -0
- package/src/infrastructure/better-auth/apple-client-secret.ts +54 -0
- package/src/infrastructure/better-auth/auth-client.ts +26 -0
- package/src/infrastructure/better-auth/options.ts +232 -0
- package/src/infrastructure/better-auth/rate-limit.ts +69 -0
- package/src/infrastructure/better-auth/secondary-storage.ts +21 -0
- package/src/infrastructure/better-auth/stripe.ts +11 -0
- package/src/infrastructure/cloudflare/auth-secondary-storage.ts +113 -0
- package/src/infrastructure/tanstack/auth.middleware.ts +89 -0
- package/src/infrastructure/tanstack/error.middleware.ts +81 -0
- package/src/infrastructure/tanstack/http-error-response.ts +30 -0
- package/src/infrastructure/tanstack/index.ts +12 -0
- package/src/infrastructure/tanstack/resolve-failure.ts +24 -0
- package/src/infrastructure/tanstack/session-cookies.ts +36 -0
- package/src/infrastructure/tanstack/to-error-payload.ts +32 -0
- package/dist/core-D3G-2gIx.js +0 -298
- package/dist/core-D3G-2gIx.js.map +0 -1
- package/dist/index-CT1yZd4D.d.ts +0 -212
- package/dist/index-CT1yZd4D.d.ts.map +0 -1
|
@@ -0,0 +1,620 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { panic } from "better-result";
|
|
3
|
+
|
|
4
|
+
//#region src/core/app-error.ts
|
|
5
|
+
/**
|
|
6
|
+
* The base of every expected failure. A subclass declares its tag and its classification,
|
|
7
|
+
* so no boundary has to guess what kind of failure it holds. A boundary tells our errors from
|
|
8
|
+
* raw throws with `instanceof AppError`; a value that fails it was never judged.
|
|
9
|
+
*
|
|
10
|
+
* A `readonly` field with a literal initializer keeps the literal type, so
|
|
11
|
+
* `readonly _tag = 'StoreUnavailable'` is enough for `Result` unions and `matchError`.
|
|
12
|
+
*/
|
|
13
|
+
var AppError = class extends Error {
|
|
14
|
+
get name() {
|
|
15
|
+
return this._tag;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/core/authentication.error.ts
|
|
21
|
+
/** A protected operation requires a session that the caller did not provide. */
|
|
22
|
+
var AuthenticationError = class extends AppError {
|
|
23
|
+
_tag = "AuthenticationError";
|
|
24
|
+
classification = "terminal";
|
|
25
|
+
constructor() {
|
|
26
|
+
super("Authentication required");
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
//#endregion
|
|
31
|
+
//#region src/core/rate-limit.error.ts
|
|
32
|
+
/** HTTP 429 with a retry delay in seconds. A null delay means the response supplies no valid duration. */
|
|
33
|
+
var RateLimitError = class extends AppError {
|
|
34
|
+
retryAfter;
|
|
35
|
+
_tag = "RateLimitError";
|
|
36
|
+
classification = "transient";
|
|
37
|
+
status = 429;
|
|
38
|
+
constructor(retryAfter) {
|
|
39
|
+
super("Too many requests");
|
|
40
|
+
this.retryAfter = retryAfter;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/core/stale-session.error.ts
|
|
46
|
+
/** A presented session cookie no longer resolves; the application owns cleanup and redirect policy. */
|
|
47
|
+
var StaleSessionError = class extends AppError {
|
|
48
|
+
_tag = "StaleSessionError";
|
|
49
|
+
classification = "terminal";
|
|
50
|
+
constructor() {
|
|
51
|
+
super("Session is no longer valid. Sign in again.");
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
//#endregion
|
|
56
|
+
//#region src/core/not-authorized.error.ts
|
|
57
|
+
/** The authenticated caller cannot perform this operation. Ported from Porte's auth.errors.ts. */
|
|
58
|
+
var NotAuthorizedError = class extends AppError {
|
|
59
|
+
_tag = "NotAuthorizedError";
|
|
60
|
+
classification = "terminal";
|
|
61
|
+
constructor() {
|
|
62
|
+
super("Not allowed");
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/core/internal.errors.ts
|
|
68
|
+
/** Safe public replacement for an unknown failure, ported from Porte's internal.errors.ts. */
|
|
69
|
+
var InternalServerError = class extends AppError {
|
|
70
|
+
_tag = "InternalServerError";
|
|
71
|
+
classification = "unknown";
|
|
72
|
+
constructor() {
|
|
73
|
+
super("Something went wrong");
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
/** Safe public replacement for a classified transient failure. */
|
|
77
|
+
var ServiceUnavailableError = class extends AppError {
|
|
78
|
+
_tag = "ServiceUnavailableError";
|
|
79
|
+
classification = "transient";
|
|
80
|
+
constructor() {
|
|
81
|
+
super("Try again shortly");
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/core/request.errors.ts
|
|
87
|
+
/** Invalid user input, one entry per invalid value in the RFC 9457 shape. Technical failures use another error. */
|
|
88
|
+
var ValidationError = class ValidationError extends AppError {
|
|
89
|
+
errors;
|
|
90
|
+
_tag = "ValidationError";
|
|
91
|
+
classification = "terminal";
|
|
92
|
+
constructor(errors, message = "The request was not valid") {
|
|
93
|
+
super(message);
|
|
94
|
+
this.errors = errors;
|
|
95
|
+
}
|
|
96
|
+
/** Convert a Zod error. Each issue path becomes a JSON Pointer. */
|
|
97
|
+
static fromZod(error, message) {
|
|
98
|
+
return ValidationError.fromIssues(error.issues, message);
|
|
99
|
+
}
|
|
100
|
+
/** Convert issues from any validator, such as issues TanStack serialized into an error message. */
|
|
101
|
+
static fromIssues(issues, message) {
|
|
102
|
+
return new ValidationError(issues.map((issue) => ({
|
|
103
|
+
detail: issue.message,
|
|
104
|
+
pointer: jsonPointer(issue.path)
|
|
105
|
+
})), message);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
/** The server cannot parse the request syntax. */
|
|
109
|
+
var MalformedRequestError = class extends AppError {
|
|
110
|
+
_tag = "MalformedRequestError";
|
|
111
|
+
classification = "terminal";
|
|
112
|
+
constructor(args) {
|
|
113
|
+
super("The request was malformed", args);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
/** No resource matches the requested HTTP path. */
|
|
117
|
+
var ResourceNotFoundError = class extends AppError {
|
|
118
|
+
_tag = "ResourceNotFoundError";
|
|
119
|
+
classification = "terminal";
|
|
120
|
+
constructor() {
|
|
121
|
+
super("Resource not found");
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
/** A WebSocket route received a request without an upgrade. */
|
|
125
|
+
var UpgradeRequiredError = class extends AppError {
|
|
126
|
+
_tag = "UpgradeRequiredError";
|
|
127
|
+
classification = "terminal";
|
|
128
|
+
constructor() {
|
|
129
|
+
super("WebSocket upgrade required");
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
/** The requested operation did not answer before its deadline. */
|
|
133
|
+
var RequestTimeoutError = class extends AppError {
|
|
134
|
+
_tag = "RequestTimeoutError";
|
|
135
|
+
classification = "transient";
|
|
136
|
+
constructor() {
|
|
137
|
+
super("The request timed out");
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
const readablePathSchema = z.array(z.union([z.string(), z.number()]));
|
|
141
|
+
function jsonPointer(path) {
|
|
142
|
+
const parsed = readablePathSchema.safeParse(path);
|
|
143
|
+
const steps = parsed.success ? parsed.data : [];
|
|
144
|
+
if (steps.length === 0) return "#";
|
|
145
|
+
return `#/${steps.map((step) => String(step).replaceAll("~", "~0").replaceAll("/", "~1")).join("/")}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
//#endregion
|
|
149
|
+
//#region src/core/problem-details.ts
|
|
150
|
+
/**
|
|
151
|
+
* The standard RFC 9457 body for a failed HTTP response.
|
|
152
|
+
* The HTTP response status remains authoritative; extensions may add problem-specific data.
|
|
153
|
+
* @see https://http.dev/problem-details
|
|
154
|
+
*/
|
|
155
|
+
const ProblemDetailsSchema = z.looseObject({
|
|
156
|
+
/**
|
|
157
|
+
* Stable URI for the problem type; use `about:blank` when the HTTP status is sufficient.
|
|
158
|
+
* @example "about:blank"
|
|
159
|
+
*/
|
|
160
|
+
type: z.string(),
|
|
161
|
+
/**
|
|
162
|
+
* Stable human-readable name for the problem type.
|
|
163
|
+
* @example "Service Unavailable"
|
|
164
|
+
*/
|
|
165
|
+
title: z.string(),
|
|
166
|
+
/**
|
|
167
|
+
* Safe human-readable explanation for this occurrence; clients must not parse it.
|
|
168
|
+
* @example "The service is unavailable."
|
|
169
|
+
*/
|
|
170
|
+
detail: z.string().optional(),
|
|
171
|
+
/**
|
|
172
|
+
* URI reference for this occurrence; omit it when no correlation resource exists.
|
|
173
|
+
* @example "/problems/occurrences/01K5F6Y7"
|
|
174
|
+
*/
|
|
175
|
+
instance: z.string().optional()
|
|
176
|
+
});
|
|
177
|
+
/** One invalid value in a `422` validation problem. */
|
|
178
|
+
const ValidationProblemErrorSchema = z.object({
|
|
179
|
+
/**
|
|
180
|
+
* Safe explanation of why this value is invalid.
|
|
181
|
+
* @example "Must be a positive integer."
|
|
182
|
+
*/
|
|
183
|
+
detail: z.string(),
|
|
184
|
+
/**
|
|
185
|
+
* RFC 6901 JSON Pointer fragment for the invalid value.
|
|
186
|
+
* @example "#/age"
|
|
187
|
+
*/
|
|
188
|
+
pointer: z.string().regex(/^#(?:\/(?:[^~]|~[01])*)*$/u)
|
|
189
|
+
});
|
|
190
|
+
/** An RFC 9457 validation problem with one or more invalid content values. */
|
|
191
|
+
const ValidationProblemDetailsSchema = ProblemDetailsSchema.extend({ errors: z.array(ValidationProblemErrorSchema).min(1) });
|
|
192
|
+
|
|
193
|
+
//#endregion
|
|
194
|
+
//#region src/core/error-table.ts
|
|
195
|
+
function entriesOf(entries) {
|
|
196
|
+
return entries;
|
|
197
|
+
}
|
|
198
|
+
const ValidationFields = z.object({ errors: z.array(ValidationProblemErrorSchema) });
|
|
199
|
+
const RateLimitFields = z.object({ retryAfter: z.number().nullable() });
|
|
200
|
+
/** The errors every application can throw, with their statuses. `defineErrors` includes them. */
|
|
201
|
+
const sharedErrors = entriesOf({
|
|
202
|
+
MalformedRequestError: {
|
|
203
|
+
error: MalformedRequestError,
|
|
204
|
+
status: 400
|
|
205
|
+
},
|
|
206
|
+
AuthenticationError: {
|
|
207
|
+
error: AuthenticationError,
|
|
208
|
+
status: 401
|
|
209
|
+
},
|
|
210
|
+
StaleSessionError: {
|
|
211
|
+
error: StaleSessionError,
|
|
212
|
+
status: 401
|
|
213
|
+
},
|
|
214
|
+
NotAuthorizedError: {
|
|
215
|
+
error: NotAuthorizedError,
|
|
216
|
+
status: 403
|
|
217
|
+
},
|
|
218
|
+
ResourceNotFoundError: {
|
|
219
|
+
error: ResourceNotFoundError,
|
|
220
|
+
status: 404
|
|
221
|
+
},
|
|
222
|
+
ValidationError: {
|
|
223
|
+
error: ValidationError,
|
|
224
|
+
status: 422,
|
|
225
|
+
fields: ValidationFields
|
|
226
|
+
},
|
|
227
|
+
UpgradeRequiredError: {
|
|
228
|
+
error: UpgradeRequiredError,
|
|
229
|
+
status: 426
|
|
230
|
+
},
|
|
231
|
+
RateLimitError: {
|
|
232
|
+
error: RateLimitError,
|
|
233
|
+
status: 429,
|
|
234
|
+
fields: RateLimitFields
|
|
235
|
+
},
|
|
236
|
+
InternalServerError: {
|
|
237
|
+
error: InternalServerError,
|
|
238
|
+
status: 500
|
|
239
|
+
},
|
|
240
|
+
ServiceUnavailableError: {
|
|
241
|
+
error: ServiceUnavailableError,
|
|
242
|
+
status: 503
|
|
243
|
+
},
|
|
244
|
+
RequestTimeoutError: {
|
|
245
|
+
error: RequestTimeoutError,
|
|
246
|
+
status: 504
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
const RemoteErrorSchema = z.object({
|
|
250
|
+
_tag: z.string(),
|
|
251
|
+
message: z.string(),
|
|
252
|
+
remote: z.literal(true)
|
|
253
|
+
});
|
|
254
|
+
function match(byTag, cause) {
|
|
255
|
+
if (cause instanceof AppError) {
|
|
256
|
+
const entry = byTag.get(cause._tag);
|
|
257
|
+
if (entry === void 0 || !(cause instanceof entry.error)) return null;
|
|
258
|
+
return {
|
|
259
|
+
tag: cause._tag,
|
|
260
|
+
entry,
|
|
261
|
+
error: cause
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
if (!(cause instanceof Error)) return null;
|
|
265
|
+
const remote = RemoteErrorSchema.safeParse(cause);
|
|
266
|
+
if (!remote.success) return null;
|
|
267
|
+
const entry = byTag.get(remote.data._tag);
|
|
268
|
+
return entry === void 0 ? null : {
|
|
269
|
+
tag: remote.data._tag,
|
|
270
|
+
entry,
|
|
271
|
+
error: cause
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Build the application's error table. The shared entries are always included;
|
|
276
|
+
* an entry with the same key replaces the shared one.
|
|
277
|
+
*/
|
|
278
|
+
function defineErrors(entries) {
|
|
279
|
+
const merged = {
|
|
280
|
+
...sharedErrors,
|
|
281
|
+
...entries
|
|
282
|
+
};
|
|
283
|
+
const byTag = new Map(Object.entries(merged));
|
|
284
|
+
const resolve = (cause) => {
|
|
285
|
+
const found = match(byTag, cause);
|
|
286
|
+
if (found === null) return null;
|
|
287
|
+
const fields = found.entry.fields?.safeParse(found.error);
|
|
288
|
+
if (fields !== void 0 && !fields.success) return null;
|
|
289
|
+
const payload = {
|
|
290
|
+
_tag: found.tag,
|
|
291
|
+
message: found.error.message,
|
|
292
|
+
...fields?.data
|
|
293
|
+
};
|
|
294
|
+
return {
|
|
295
|
+
status: found.entry.status,
|
|
296
|
+
payload
|
|
297
|
+
};
|
|
298
|
+
};
|
|
299
|
+
const resolveShared = (error) => resolve(error) ?? panic(`Error table cannot resolve shared error ${error._tag}`);
|
|
300
|
+
const generic = resolveShared(new InternalServerError());
|
|
301
|
+
const unavailable = resolveShared(new ServiceUnavailableError());
|
|
302
|
+
const [first, ...rest] = [...byTag].map(([tag, entry]) => z.strictObject({
|
|
303
|
+
_tag: z.literal(tag),
|
|
304
|
+
message: z.string(),
|
|
305
|
+
...entry.fields?.shape
|
|
306
|
+
}));
|
|
307
|
+
if (first === void 0) return panic("Error table has no entries");
|
|
308
|
+
const payloadSchema = z.discriminatedUnion("_tag", [first, ...rest]).pipe(z.custom());
|
|
309
|
+
const parse = (cause) => {
|
|
310
|
+
const parsed = payloadSchema.safeParse(cause);
|
|
311
|
+
return parsed.success ? parsed.data : null;
|
|
312
|
+
};
|
|
313
|
+
return {
|
|
314
|
+
entries: merged,
|
|
315
|
+
payloadSchema,
|
|
316
|
+
generic: generic.payload,
|
|
317
|
+
parse,
|
|
318
|
+
is(cause, tag) {
|
|
319
|
+
const parsed = parse(cause);
|
|
320
|
+
return parsed !== null && parsed._tag === tag;
|
|
321
|
+
},
|
|
322
|
+
resolve,
|
|
323
|
+
fallback: (cause) => cause instanceof AppError && cause.classification === "transient" ? unavailable : generic
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
//#endregion
|
|
328
|
+
//#region src/core/database.error.ts
|
|
329
|
+
/** Classification describes the failure; callers must separately establish whether repetition is safe. */
|
|
330
|
+
var DatabaseError = class extends AppError {
|
|
331
|
+
operation;
|
|
332
|
+
classification;
|
|
333
|
+
details;
|
|
334
|
+
_tag = "DatabaseError";
|
|
335
|
+
constructor(operation, classification, details, options) {
|
|
336
|
+
super(`Database operation failed: ${operation}`, options);
|
|
337
|
+
this.operation = operation;
|
|
338
|
+
this.classification = classification;
|
|
339
|
+
this.details = details;
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
//#endregion
|
|
344
|
+
//#region src/core/defects.ts
|
|
345
|
+
/**
|
|
346
|
+
* Mark a line the types say cannot run. A union member without a branch stops compilation here.
|
|
347
|
+
*
|
|
348
|
+
* @throws Panic when a value outside the union arrives at runtime.
|
|
349
|
+
*/
|
|
350
|
+
function unreachable(value) {
|
|
351
|
+
return panic(`Unreachable: ${String(value)}`);
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Mark a body that is not written yet. A defect, not an expected failure.
|
|
355
|
+
*
|
|
356
|
+
* @param what - The behavior the body will provide, such as `invoice export`.
|
|
357
|
+
* @throws Panic always.
|
|
358
|
+
*/
|
|
359
|
+
function notImplemented(what) {
|
|
360
|
+
return panic(`Not implemented: ${what}`);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
//#endregion
|
|
364
|
+
//#region src/core/failure-classification.ts
|
|
365
|
+
/** Decide whether one retry owner may repeat a failed operation. */
|
|
366
|
+
function shouldRetryFailure(args) {
|
|
367
|
+
if (!args.repeatSafe) return false;
|
|
368
|
+
if (args.classification === "transient") return true;
|
|
369
|
+
if (args.classification === "terminal") return false;
|
|
370
|
+
return args.owner === "durable";
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
//#endregion
|
|
374
|
+
//#region src/core/transport-classification.ts
|
|
375
|
+
/**
|
|
376
|
+
* Messages that the platform-neutral transports raise for a failure that may pass.
|
|
377
|
+
*
|
|
378
|
+
* A boundary that knows its own runtime adds its own patterns before these.
|
|
379
|
+
*/
|
|
380
|
+
const TRANSIENT_TRANSPORT_PATTERNS = [
|
|
381
|
+
/network/,
|
|
382
|
+
/fetch failed/,
|
|
383
|
+
/timeout/,
|
|
384
|
+
/timed?\s*out/,
|
|
385
|
+
/connection.*(lost|reset|refused|closed|aborted)/,
|
|
386
|
+
/econnreset/,
|
|
387
|
+
/econnrefused/,
|
|
388
|
+
/etimedout/,
|
|
389
|
+
/eai_again/
|
|
390
|
+
];
|
|
391
|
+
/** Cancellation is a decision, not a failure to repeat. */
|
|
392
|
+
function isAbortError(cause) {
|
|
393
|
+
return cause instanceof Error && cause.name === "AbortError";
|
|
394
|
+
}
|
|
395
|
+
/** Match known transient transport messages while excluding explicit cancellation. */
|
|
396
|
+
function isTransientTransportError(cause) {
|
|
397
|
+
if (!(cause instanceof Error) || isAbortError(cause)) return false;
|
|
398
|
+
const message = cause.message.toLowerCase();
|
|
399
|
+
return TRANSIENT_TRANSPORT_PATTERNS.some((pattern) => pattern.test(message));
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
//#endregion
|
|
403
|
+
//#region src/core/logger.ts
|
|
404
|
+
/**
|
|
405
|
+
* The supported log severity levels, in ascending order.
|
|
406
|
+
*
|
|
407
|
+
* A const object, not an enum, so a parsed environment value typed `'INFO'` is a `LogLevel`.
|
|
408
|
+
*/
|
|
409
|
+
const LogLevel = {
|
|
410
|
+
DEBUG: "DEBUG",
|
|
411
|
+
INFO: "INFO",
|
|
412
|
+
WARN: "WARN",
|
|
413
|
+
ERROR: "ERROR"
|
|
414
|
+
};
|
|
415
|
+
const DEFAULT_SETTINGS = {
|
|
416
|
+
level: LogLevel.INFO,
|
|
417
|
+
format: "json"
|
|
418
|
+
};
|
|
419
|
+
let settings = DEFAULT_SETTINGS;
|
|
420
|
+
/**
|
|
421
|
+
* Set application settings, including for existing loggers. Per-logger overrides take priority.
|
|
422
|
+
* Call at startup, never per request. An omitted field resets to INFO and JSON output.
|
|
423
|
+
*/
|
|
424
|
+
function configureLogger(config) {
|
|
425
|
+
settings = {
|
|
426
|
+
level: config.level ?? DEFAULT_SETTINGS.level,
|
|
427
|
+
format: config.format ?? DEFAULT_SETTINGS.format
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
let errorHook = null;
|
|
431
|
+
/**
|
|
432
|
+
* Register the function that receives each logged error.
|
|
433
|
+
*
|
|
434
|
+
* Set once at the process entry point. A later call replaces the earlier hook.
|
|
435
|
+
*/
|
|
436
|
+
function setLoggerErrorHook(fn) {
|
|
437
|
+
errorHook = fn;
|
|
438
|
+
}
|
|
439
|
+
const CONSOLE_METHOD = {
|
|
440
|
+
[LogLevel.DEBUG]: "debug",
|
|
441
|
+
[LogLevel.INFO]: "info",
|
|
442
|
+
[LogLevel.WARN]: "warn",
|
|
443
|
+
[LogLevel.ERROR]: "error"
|
|
444
|
+
};
|
|
445
|
+
const ESC = String.fromCharCode(27);
|
|
446
|
+
const ANSI_RESET = `${ESC}[0m`;
|
|
447
|
+
const ANSI_GRAY = `${ESC}[90m`;
|
|
448
|
+
const ANSI_BLUE = `${ESC}[34m`;
|
|
449
|
+
const LEVEL_ANSI = {
|
|
450
|
+
DEBUG: `${ESC}[2;36m`,
|
|
451
|
+
INFO: ANSI_BLUE,
|
|
452
|
+
WARN: `${ESC}[33m`,
|
|
453
|
+
ERROR: `${ESC}[1;31m`
|
|
454
|
+
};
|
|
455
|
+
function prettyLine(entry) {
|
|
456
|
+
const level = `[${entry.level}]`;
|
|
457
|
+
const module = `[${entry.module}]`;
|
|
458
|
+
if ("window" in globalThis) return `${entry.timestamp} ${level} ${module} ${entry.message}`;
|
|
459
|
+
return `${`${ANSI_GRAY}${entry.timestamp}${ANSI_RESET}`} ${`${LEVEL_ANSI[entry.level]}${level}${ANSI_RESET}`} ${`${ANSI_BLUE}${module}${ANSI_RESET}`} ${entry.message}`;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* JSON format writes the entry object. Pretty format writes one line, then the details,
|
|
463
|
+
* the error, and the stack as separate console arguments.
|
|
464
|
+
*/
|
|
465
|
+
const consoleSink = (entry) => {
|
|
466
|
+
const write = console[CONSOLE_METHOD[entry.level]];
|
|
467
|
+
if (settings.format === "json") {
|
|
468
|
+
write(entry);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const line = prettyLine(entry);
|
|
472
|
+
const fields = entry.userId === void 0 ? entry.details : {
|
|
473
|
+
...entry.details,
|
|
474
|
+
userId: entry.userId
|
|
475
|
+
};
|
|
476
|
+
const details = Object.keys(fields).length === 0 ? [] : [fields];
|
|
477
|
+
if (entry.error === void 0) {
|
|
478
|
+
write(line, ...details);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
const { stack, ...error } = entry.error;
|
|
482
|
+
if (stack === void 0) write(line, ...details, error);
|
|
483
|
+
else write(line, ...details, error, `\n${stack}`);
|
|
484
|
+
};
|
|
485
|
+
let logSink = consoleSink;
|
|
486
|
+
/**
|
|
487
|
+
* Send every log entry somewhere other than `console`.
|
|
488
|
+
*
|
|
489
|
+
* Set once at the process entry point, never per module. A logger is created
|
|
490
|
+
* by name and nothing else, so its destination is a fact about the process.
|
|
491
|
+
*/
|
|
492
|
+
function setLogSink(fn) {
|
|
493
|
+
logSink = fn;
|
|
494
|
+
}
|
|
495
|
+
const LOG_LEVELS = Object.values(LogLevel);
|
|
496
|
+
const MAX_ERROR_DEPTH = 3;
|
|
497
|
+
/**
|
|
498
|
+
* Diagnostics worth keeping off an error, named one by one.
|
|
499
|
+
*
|
|
500
|
+
* An allowlist rather than every own property: an error raised by a library we
|
|
501
|
+
* do not control may hang a request or a user payload off itself, and a log is
|
|
502
|
+
* the wrong place to discover that. `_tag` and `classification` are what every `AppError`
|
|
503
|
+
* declares. `remote` and the three flags after it are the ones workerd sets itself.
|
|
504
|
+
*/
|
|
505
|
+
const KEPT_ERROR_FIELDS = [
|
|
506
|
+
"_tag",
|
|
507
|
+
"classification",
|
|
508
|
+
"code",
|
|
509
|
+
"operation",
|
|
510
|
+
"status",
|
|
511
|
+
"statusCode",
|
|
512
|
+
"remote",
|
|
513
|
+
"retryable",
|
|
514
|
+
"overloaded",
|
|
515
|
+
"durableObjectReset"
|
|
516
|
+
];
|
|
517
|
+
const serializeError = (error, depth = 0) => {
|
|
518
|
+
if (depth > MAX_ERROR_DEPTH) return {
|
|
519
|
+
name: "Error",
|
|
520
|
+
message: "[error nesting truncated]"
|
|
521
|
+
};
|
|
522
|
+
const carrier = error;
|
|
523
|
+
const kept = KEPT_ERROR_FIELDS.filter((key) => carrier[key] !== void 0);
|
|
524
|
+
const serialized = {
|
|
525
|
+
name: error.name,
|
|
526
|
+
message: error.message
|
|
527
|
+
};
|
|
528
|
+
if (error.stack !== void 0) serialized.stack = error.stack;
|
|
529
|
+
if (kept.length > 0) serialized.fields = Object.fromEntries(kept.map((key) => [key, carrier[key]]));
|
|
530
|
+
if (error.cause instanceof Error) serialized.cause = serializeError(error.cause, depth + 1);
|
|
531
|
+
else if (error.cause !== void 0) serialized.cause = {
|
|
532
|
+
name: "Error",
|
|
533
|
+
message: "[non-error cause]"
|
|
534
|
+
};
|
|
535
|
+
if (error instanceof AggregateError) serialized.errors = error.errors.map((failure) => failure instanceof Error ? serializeError(failure, depth + 1) : {
|
|
536
|
+
name: "NonError",
|
|
537
|
+
message: "[non-error aggregate member]"
|
|
538
|
+
});
|
|
539
|
+
return serialized;
|
|
540
|
+
};
|
|
541
|
+
/** Write module-scoped logs with application defaults and optional overrides. */
|
|
542
|
+
var Logger = class {
|
|
543
|
+
module;
|
|
544
|
+
config;
|
|
545
|
+
/** Create a logger for one module. */
|
|
546
|
+
constructor(module, overrideConfig) {
|
|
547
|
+
this.module = module;
|
|
548
|
+
this.config = { ...overrideConfig };
|
|
549
|
+
}
|
|
550
|
+
log(level, message, details, error, userId) {
|
|
551
|
+
const threshold = this.config.level ?? settings.level;
|
|
552
|
+
if (threshold === "OFF") return;
|
|
553
|
+
if (LOG_LEVELS.indexOf(level) < LOG_LEVELS.indexOf(threshold)) return;
|
|
554
|
+
let entry = {
|
|
555
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
556
|
+
level,
|
|
557
|
+
module: this.module,
|
|
558
|
+
message,
|
|
559
|
+
details
|
|
560
|
+
};
|
|
561
|
+
if (userId !== void 0) entry = {
|
|
562
|
+
...entry,
|
|
563
|
+
userId
|
|
564
|
+
};
|
|
565
|
+
logSink(error === void 0 ? entry : {
|
|
566
|
+
...entry,
|
|
567
|
+
error
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
/** Write a debug log. */
|
|
571
|
+
debug(message, details = {}) {
|
|
572
|
+
this.log(LogLevel.DEBUG, message, details);
|
|
573
|
+
}
|
|
574
|
+
/** Write an information log. */
|
|
575
|
+
info(message, details = {}) {
|
|
576
|
+
this.log(LogLevel.INFO, message, details);
|
|
577
|
+
}
|
|
578
|
+
/** Write a warning log. */
|
|
579
|
+
warn(message, details = {}) {
|
|
580
|
+
this.log(LogLevel.WARN, message, details);
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Write an error log and send the error to the configured hook.
|
|
584
|
+
*
|
|
585
|
+
* The details say which call this was, and the error says what went wrong
|
|
586
|
+
* inside it. Both the entry and the error hook receive the supplied user ID.
|
|
587
|
+
*/
|
|
588
|
+
error(message, context = {}) {
|
|
589
|
+
const details = context.details ?? {};
|
|
590
|
+
const thrown = context.error;
|
|
591
|
+
let error;
|
|
592
|
+
if (thrown instanceof Error) error = serializeError(thrown);
|
|
593
|
+
else if (thrown !== void 0) {
|
|
594
|
+
let json;
|
|
595
|
+
try {
|
|
596
|
+
json = JSON.stringify(thrown);
|
|
597
|
+
} catch {
|
|
598
|
+
json = "[non-serializable error]";
|
|
599
|
+
}
|
|
600
|
+
error = {
|
|
601
|
+
name: "NonError",
|
|
602
|
+
message: json
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
this.log(LogLevel.ERROR, message, details, error, context.userId);
|
|
606
|
+
if (errorHook !== null && thrown !== void 0) errorHook({
|
|
607
|
+
error: thrown,
|
|
608
|
+
distinctId: context.userId,
|
|
609
|
+
context: details
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
/** Create a logger for one module. */
|
|
614
|
+
function createLogger(module, config) {
|
|
615
|
+
return new Logger(module, config);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
//#endregion
|
|
619
|
+
export { AppError as A, ValidationError as C, StaleSessionError as D, NotAuthorizedError as E, RateLimitError as O, UpgradeRequiredError as S, ServiceUnavailableError as T, ValidationProblemDetailsSchema as _, createLogger as a, RequestTimeoutError as b, isAbortError as c, notImplemented as d, unreachable as f, ProblemDetailsSchema as g, sharedErrors as h, consoleSink as i, AuthenticationError as k, isTransientTransportError as l, defineErrors as m, Logger as n, setLogSink as o, DatabaseError as p, configureLogger as r, setLoggerErrorHook as s, LogLevel as t, shouldRetryFailure as u, ValidationProblemErrorSchema as v, InternalServerError as w, ResourceNotFoundError as x, MalformedRequestError as y };
|
|
620
|
+
//# sourceMappingURL=core-CnwWj4MY.js.map
|