better-ship 0.25.0 → 0.27.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/auth-secondary-storage-b9ptwPSB.d.ts +37 -0
- package/dist/auth-secondary-storage-b9ptwPSB.d.ts.map +1 -0
- package/dist/better-auth/secondary-storage.d.ts +1 -1
- package/dist/bin/notify-ci.d.ts +1 -0
- package/dist/bin/notify-ci.js +24 -0
- package/dist/bin/notify-ci.js.map +1 -0
- package/dist/client-Dv1Ch7yY.js +20 -0
- package/dist/client-Dv1Ch7yY.js.map +1 -0
- package/dist/cloudflare/auth-secondary-storage.d.ts +2 -36
- package/dist/cloudflare/durable-object-client.js +1 -1
- package/dist/cloudflare/durable-object-errors.js +2 -47
- package/dist/cloudflare/kv-client.js +1 -1
- package/dist/cloudflare/kv-errors.js +2 -143
- package/dist/cloudflare/r2-client.js +1 -1
- package/dist/cloudflare/r2-errors.js +2 -89
- package/dist/cloudflare/turnstile.d.ts +57 -0
- package/dist/cloudflare/turnstile.d.ts.map +1 -0
- package/dist/cloudflare/turnstile.js +191 -0
- package/dist/cloudflare/turnstile.js.map +1 -0
- package/dist/durable-object-errors-DkMFXQnK.js +48 -0
- package/dist/durable-object-errors-DkMFXQnK.js.map +1 -0
- package/dist/kv-errors-BUTpFy0t.js +144 -0
- package/dist/kv-errors-BUTpFy0t.js.map +1 -0
- package/dist/metrics-CWv37yf9.js +30 -0
- package/dist/metrics-CWv37yf9.js.map +1 -0
- package/dist/metrics-cv9P5nvk.d.ts +19 -0
- package/dist/metrics-cv9P5nvk.d.ts.map +1 -0
- package/dist/payment-service-DUYd3Itt.d.ts +52 -0
- package/dist/payment-service-DUYd3Itt.d.ts.map +1 -0
- package/dist/payment-service.error-CqF7GkNp.js +30 -0
- package/dist/payment-service.error-CqF7GkNp.js.map +1 -0
- package/dist/posthog/metrics.d.ts +2 -18
- package/dist/posthog/metrics.js +2 -29
- package/dist/posthog/server.d.ts +1 -1
- package/dist/posthog/server.js +1 -1
- package/dist/r2-errors-rk7n7rvk.js +90 -0
- package/dist/r2-errors-rk7n7rvk.js.map +1 -0
- package/dist/request-4vJEWxyZ.js +21 -0
- package/dist/request-4vJEWxyZ.js.map +1 -0
- package/dist/slack-webhook-j1FCPeu8.js +270 -0
- package/dist/slack-webhook-j1FCPeu8.js.map +1 -0
- package/dist/slack.d.ts +102 -0
- package/dist/slack.d.ts.map +1 -0
- package/dist/slack.js +3 -0
- package/dist/stripe/client.js +2 -19
- package/dist/stripe/payment-service.d.ts +2 -51
- package/dist/stripe/payment-service.error.js +2 -29
- package/dist/stripe/payment-service.js +1 -1
- package/dist/stripe/request.js +2 -20
- package/dist/stripe-fixture-yFUknTJO.js +33 -0
- package/dist/stripe-fixture-yFUknTJO.js.map +1 -0
- package/dist/testing/payment-service.d.ts +1 -1
- package/dist/testing/payment-service.js +2 -2
- package/dist/testing/stripe.js +2 -32
- package/package.json +7 -1
- package/src/infrastructure/cloudflare/turnstile/index.ts +7 -0
- package/src/infrastructure/cloudflare/turnstile/turnstile.error.ts +21 -0
- package/src/infrastructure/cloudflare/turnstile/turnstile.ts +261 -0
- package/src/infrastructure/slack/ci-message.ts +276 -0
- package/src/infrastructure/slack/index.ts +10 -0
- package/src/infrastructure/slack/notify-ci.ts +22 -0
- package/src/infrastructure/slack/slack-webhook.ts +36 -0
- package/dist/cloudflare/auth-secondary-storage.d.ts.map +0 -1
- package/dist/cloudflare/durable-object-errors.js.map +0 -1
- package/dist/cloudflare/kv-errors.js.map +0 -1
- package/dist/cloudflare/r2-errors.js.map +0 -1
- package/dist/posthog/metrics.d.ts.map +0 -1
- package/dist/posthog/metrics.js.map +0 -1
- package/dist/stripe/client.js.map +0 -1
- package/dist/stripe/payment-service.d.ts.map +0 -1
- package/dist/stripe/payment-service.error.js.map +0 -1
- package/dist/stripe/request.js.map +0 -1
- package/dist/testing/stripe.js.map +0 -1
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { b as shouldRetryFailure, c as exponentialEqualJitterMs, nt as AppError, u as classifyBoundaryError } from "../core-BiN6S_cm.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { Result } from "better-result";
|
|
4
|
+
|
|
5
|
+
//#region src/infrastructure/cloudflare/turnstile/turnstile.error.ts
|
|
6
|
+
/**
|
|
7
|
+
* Siteverify did not produce a verdict.
|
|
8
|
+
* A rejected token is not this error. The caller logs it once.
|
|
9
|
+
*/
|
|
10
|
+
var TurnstileVerificationError = class extends AppError {
|
|
11
|
+
_tag = "TurnstileVerificationError";
|
|
12
|
+
classification;
|
|
13
|
+
errorCodes;
|
|
14
|
+
constructor(args) {
|
|
15
|
+
super("Turnstile siteverify could not produce a verdict", { cause: args.cause });
|
|
16
|
+
this.classification = args.classification;
|
|
17
|
+
this.errorCodes = args.errorCodes;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/infrastructure/cloudflare/turnstile/turnstile.ts
|
|
23
|
+
/**
|
|
24
|
+
* Turnstile siteverify. Ported from Suparelay `verify-turnstile.ts` and Typist `turnstile.ts`.
|
|
25
|
+
* A rejection is a verdict. This module does not log, and one call retries with one idempotency key.
|
|
26
|
+
*/
|
|
27
|
+
const SITEVERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify";
|
|
28
|
+
const MAX_TOKEN_LENGTH = 2048;
|
|
29
|
+
const DEFAULT_TIMEOUT_MS = 1e4;
|
|
30
|
+
const RETRY_TIMES = 2;
|
|
31
|
+
const CONFIGURATION_CODES = /* @__PURE__ */ new Set([
|
|
32
|
+
"missing-input-secret",
|
|
33
|
+
"invalid-input-secret",
|
|
34
|
+
"bad-request"
|
|
35
|
+
]);
|
|
36
|
+
const SuccessBody = z.object({
|
|
37
|
+
success: z.literal(true),
|
|
38
|
+
hostname: z.string(),
|
|
39
|
+
challenge_ts: z.string(),
|
|
40
|
+
action: z.string().optional(),
|
|
41
|
+
metadata: z.object({ result_with_testing_key: z.boolean().optional() }).optional()
|
|
42
|
+
});
|
|
43
|
+
const FailureBody = z.object({
|
|
44
|
+
success: z.literal(false),
|
|
45
|
+
"error-codes": z.array(z.string()).default([])
|
|
46
|
+
});
|
|
47
|
+
var SiteverifyRetry = class extends Error {
|
|
48
|
+
errorCodes;
|
|
49
|
+
_tag = "SiteverifyRetry";
|
|
50
|
+
constructor(errorCodes, cause) {
|
|
51
|
+
super("Turnstile siteverify did not answer", { cause });
|
|
52
|
+
this.errorCodes = errorCodes;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Ask siteverify whether this token is a solved challenge.
|
|
57
|
+
* A rejection is a verdict. No verdict throws `TurnstileVerificationError`.
|
|
58
|
+
* A transient failure retries twice with the same idempotency key.
|
|
59
|
+
*/
|
|
60
|
+
async function verifyTurnstile(check, options = {}) {
|
|
61
|
+
if (check.token.length > MAX_TOKEN_LENGTH) return {
|
|
62
|
+
_tag: "Rejected",
|
|
63
|
+
errorCodes: ["invalid-input-response"]
|
|
64
|
+
};
|
|
65
|
+
const idempotencyKey = resolveIdempotencyKey(check.idempotencyKey);
|
|
66
|
+
const result = await Result.tryPromise({
|
|
67
|
+
try: () => requestVerdict(check, idempotencyKey, options),
|
|
68
|
+
catch: (cause) => cause
|
|
69
|
+
}, { retry: {
|
|
70
|
+
times: RETRY_TIMES,
|
|
71
|
+
delayMs: (_cause, context) => exponentialEqualJitterMs(25, 200, context.attempt),
|
|
72
|
+
shouldRetry: (cause) => cause instanceof SiteverifyRetry && shouldRetryFailure({
|
|
73
|
+
classification: "transient",
|
|
74
|
+
repeatSafe: true,
|
|
75
|
+
owner: "immediate"
|
|
76
|
+
})
|
|
77
|
+
} });
|
|
78
|
+
if (result.isOk()) return result.value;
|
|
79
|
+
if (result.error instanceof TurnstileVerificationError) throw result.error;
|
|
80
|
+
if (result.error instanceof SiteverifyRetry) throw new TurnstileVerificationError({
|
|
81
|
+
classification: "transient",
|
|
82
|
+
errorCodes: result.error.errorCodes,
|
|
83
|
+
cause: result.error.cause
|
|
84
|
+
});
|
|
85
|
+
throw new TurnstileVerificationError({
|
|
86
|
+
classification: "unknown",
|
|
87
|
+
errorCodes: [],
|
|
88
|
+
cause: result.error
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function resolveIdempotencyKey(key) {
|
|
92
|
+
if (key === void 0) return crypto.randomUUID();
|
|
93
|
+
if (z.uuid().safeParse(key).success) return key;
|
|
94
|
+
throw new TurnstileVerificationError({
|
|
95
|
+
classification: "terminal",
|
|
96
|
+
errorCodes: [],
|
|
97
|
+
cause: { idempotencyKey: "invalid" }
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
async function requestVerdict(check, idempotencyKey, options) {
|
|
101
|
+
const fetchImpl = options.fetch ?? fetch;
|
|
102
|
+
const fields = {
|
|
103
|
+
secret: check.secretKey,
|
|
104
|
+
response: check.token,
|
|
105
|
+
idempotency_key: idempotencyKey
|
|
106
|
+
};
|
|
107
|
+
const payload = check.remoteIp !== void 0 && check.remoteIp !== "" ? {
|
|
108
|
+
...fields,
|
|
109
|
+
remoteip: check.remoteIp
|
|
110
|
+
} : fields;
|
|
111
|
+
let response;
|
|
112
|
+
try {
|
|
113
|
+
response = await fetchImpl(SITEVERIFY_URL, {
|
|
114
|
+
method: "POST",
|
|
115
|
+
headers: { "Content-Type": "application/json" },
|
|
116
|
+
body: JSON.stringify(payload),
|
|
117
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? DEFAULT_TIMEOUT_MS)
|
|
118
|
+
});
|
|
119
|
+
} catch (cause) {
|
|
120
|
+
throw transportFailure(cause);
|
|
121
|
+
}
|
|
122
|
+
const text = await response.text();
|
|
123
|
+
let parsed;
|
|
124
|
+
try {
|
|
125
|
+
parsed = JSON.parse(text);
|
|
126
|
+
} catch (cause) {
|
|
127
|
+
throw unparsedFailure(response.status, cause);
|
|
128
|
+
}
|
|
129
|
+
const success = SuccessBody.safeParse(parsed);
|
|
130
|
+
if (success.success) return decideSuccess(success.data, check);
|
|
131
|
+
const failure = FailureBody.safeParse(parsed);
|
|
132
|
+
if (failure.success) return decideFailure(failure.data["error-codes"], response.status);
|
|
133
|
+
throw unparsedFailure(response.status, { status: response.status });
|
|
134
|
+
}
|
|
135
|
+
function decideSuccess(body, check) {
|
|
136
|
+
if (check.expectedAction !== void 0 && body.action !== void 0 && body.action !== check.expectedAction) return {
|
|
137
|
+
_tag: "Rejected",
|
|
138
|
+
errorCodes: ["action-mismatch"]
|
|
139
|
+
};
|
|
140
|
+
if (!(body.metadata?.result_with_testing_key === true) && check.expectedHostnames !== void 0 && !check.expectedHostnames.includes(body.hostname)) return {
|
|
141
|
+
_tag: "Rejected",
|
|
142
|
+
errorCodes: ["hostname-mismatch"]
|
|
143
|
+
};
|
|
144
|
+
if (body.action === void 0) return {
|
|
145
|
+
_tag: "Accepted",
|
|
146
|
+
hostname: body.hostname,
|
|
147
|
+
challengeTs: body.challenge_ts
|
|
148
|
+
};
|
|
149
|
+
return {
|
|
150
|
+
_tag: "Accepted",
|
|
151
|
+
hostname: body.hostname,
|
|
152
|
+
challengeTs: body.challenge_ts,
|
|
153
|
+
action: body.action
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function decideFailure(errorCodes, status) {
|
|
157
|
+
if (errorCodes.some((code) => CONFIGURATION_CODES.has(code))) throw verificationFailure("terminal", errorCodes, status);
|
|
158
|
+
if (errorCodes.includes("internal-error") || status === 429 || status >= 500) throw new SiteverifyRetry(errorCodes, { status });
|
|
159
|
+
return {
|
|
160
|
+
_tag: "Rejected",
|
|
161
|
+
errorCodes
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function transportFailure(cause) {
|
|
165
|
+
const classification = classifyBoundaryError({
|
|
166
|
+
cause,
|
|
167
|
+
classifyBoundary: () => void 0,
|
|
168
|
+
includeCloudflare: false
|
|
169
|
+
});
|
|
170
|
+
if (classification === "transient") return new SiteverifyRetry([], cause);
|
|
171
|
+
return new TurnstileVerificationError({
|
|
172
|
+
classification,
|
|
173
|
+
errorCodes: [],
|
|
174
|
+
cause
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function unparsedFailure(status, cause) {
|
|
178
|
+
if (status === 429 || status >= 500) return new SiteverifyRetry([], cause);
|
|
179
|
+
return verificationFailure("terminal", [], status);
|
|
180
|
+
}
|
|
181
|
+
function verificationFailure(classification, errorCodes, status) {
|
|
182
|
+
return new TurnstileVerificationError({
|
|
183
|
+
classification,
|
|
184
|
+
errorCodes,
|
|
185
|
+
cause: { status }
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
//#endregion
|
|
190
|
+
export { TurnstileVerificationError, verifyTurnstile };
|
|
191
|
+
//# sourceMappingURL=turnstile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turnstile.js","names":[],"sources":["../../src/infrastructure/cloudflare/turnstile/turnstile.error.ts","../../src/infrastructure/cloudflare/turnstile/turnstile.ts"],"sourcesContent":["import { AppError, type FailureClassification } from '@/core'\n\n/**\n * Siteverify did not produce a verdict.\n * A rejected token is not this error. The caller logs it once.\n */\nexport class TurnstileVerificationError extends AppError {\n readonly _tag = 'TurnstileVerificationError'\n readonly classification: FailureClassification\n readonly errorCodes: readonly string[]\n\n constructor(args: {\n readonly classification: FailureClassification\n readonly errorCodes: readonly string[]\n readonly cause: unknown\n }) {\n super('Turnstile siteverify could not produce a verdict', { cause: args.cause })\n this.classification = args.classification\n this.errorCodes = args.errorCodes\n }\n}\n","import { Result } from 'better-result'\nimport { z } from 'zod'\n\nimport {\n classifyBoundaryError,\n exponentialEqualJitterMs,\n shouldRetryFailure,\n type FailureClassification,\n} from '@/core'\n\nimport { TurnstileVerificationError } from './turnstile.error.ts'\n\n/**\n * Turnstile siteverify. Ported from Suparelay `verify-turnstile.ts` and Typist `turnstile.ts`.\n * A rejection is a verdict. This module does not log, and one call retries with one idempotency key.\n */\n\nconst SITEVERIFY_URL = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'\n\n// Cloudflare documents 2048 characters. The testing secret still accepts a longer token.\nconst MAX_TOKEN_LENGTH = 2048\n\n// Cloudflare's validation example stops at 10 seconds.\nconst DEFAULT_TIMEOUT_MS = 10_000\n\n// `times` counts retries after the first attempt, so a call stops at three attempts.\nconst RETRY_TIMES = 2\n\nconst CONFIGURATION_CODES: ReadonlySet<string> = new Set([\n 'missing-input-secret',\n 'invalid-input-secret',\n 'bad-request',\n])\n\nconst SuccessBody = z.object({\n success: z.literal(true),\n hostname: z.string(),\n challenge_ts: z.string(),\n action: z.string().optional(),\n metadata: z.object({ result_with_testing_key: z.boolean().optional() }).optional(),\n})\n\nconst FailureBody = z.object({\n success: z.literal(false),\n 'error-codes': z.array(z.string()).default([]),\n})\n\ntype SuccessBody = z.infer<typeof SuccessBody>\n\n/** The values siteverify needs. The secret stays in the caller. */\nexport type TurnstileCheck = {\n readonly secretKey: string\n readonly token: string\n /** Visitor address from `CF-Connecting-IP`. Omit it when the request has none. */\n readonly remoteIp?: string\n /** Widget action. A present, different action rejects. A missing action does not. */\n readonly expectedAction?: string\n /** Allowed widget hostnames. A testing-key success skips this list. */\n readonly expectedHostnames?: readonly string[]\n /** UUID reused for this call's retries. Omit it and the call mints one. */\n readonly idempotencyKey?: string\n}\n\n/** A solved challenge, or a refusal siteverify already decided. */\nexport type TurnstileVerdict =\n | {\n readonly _tag: 'Accepted'\n readonly hostname: string\n readonly challengeTs: string\n readonly action?: string\n }\n | {\n readonly _tag: 'Rejected'\n readonly errorCodes: readonly string[]\n }\n\n/** Deadline and fetch overrides. Production calls omit this argument. */\nexport type TurnstileVerifyOptions = {\n /** Replaces global `fetch`. A test uses it for a response the testing secret cannot return. */\n readonly fetch?: typeof fetch\n /** Siteverify deadline in milliseconds. The default is 10 seconds. */\n readonly timeoutMs?: number\n}\n\nclass SiteverifyRetry extends Error {\n readonly _tag = 'SiteverifyRetry'\n\n constructor(\n readonly errorCodes: readonly string[],\n cause: unknown,\n ) {\n super('Turnstile siteverify did not answer', { cause })\n }\n}\n\n/**\n * Ask siteverify whether this token is a solved challenge.\n * A rejection is a verdict. No verdict throws `TurnstileVerificationError`.\n * A transient failure retries twice with the same idempotency key.\n */\nexport async function verifyTurnstile(\n check: TurnstileCheck,\n options: TurnstileVerifyOptions = {},\n): Promise<TurnstileVerdict> {\n if (check.token.length > MAX_TOKEN_LENGTH) {\n return { _tag: 'Rejected', errorCodes: ['invalid-input-response'] }\n }\n const idempotencyKey = resolveIdempotencyKey(check.idempotencyKey)\n const result = await Result.tryPromise(\n {\n try: () => requestVerdict(check, idempotencyKey, options),\n catch: (cause) => cause,\n },\n {\n retry: {\n times: RETRY_TIMES,\n delayMs: (_cause, context) => exponentialEqualJitterMs(25, 200, context.attempt),\n shouldRetry: (cause) =>\n cause instanceof SiteverifyRetry &&\n shouldRetryFailure({\n classification: 'transient',\n repeatSafe: true,\n owner: 'immediate',\n }),\n },\n },\n )\n if (result.isOk()) return result.value\n if (result.error instanceof TurnstileVerificationError) throw result.error\n if (result.error instanceof SiteverifyRetry) {\n throw new TurnstileVerificationError({\n classification: 'transient',\n errorCodes: result.error.errorCodes,\n cause: result.error.cause,\n })\n }\n throw new TurnstileVerificationError({\n classification: 'unknown',\n errorCodes: [],\n cause: result.error,\n })\n}\n\nfunction resolveIdempotencyKey(key: string | undefined): string {\n if (key === undefined) return crypto.randomUUID()\n if (z.uuid().safeParse(key).success) return key\n throw new TurnstileVerificationError({\n classification: 'terminal',\n errorCodes: [],\n cause: { idempotencyKey: 'invalid' },\n })\n}\n\nasync function requestVerdict(\n check: TurnstileCheck,\n idempotencyKey: string,\n options: TurnstileVerifyOptions,\n): Promise<TurnstileVerdict> {\n const fetchImpl = options.fetch ?? fetch\n const fields = {\n secret: check.secretKey,\n response: check.token,\n idempotency_key: idempotencyKey,\n }\n const payload =\n check.remoteIp !== undefined && check.remoteIp !== ''\n ? { ...fields, remoteip: check.remoteIp }\n : fields\n\n let response: Response\n try {\n response = await fetchImpl(SITEVERIFY_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n signal: AbortSignal.timeout(options.timeoutMs ?? DEFAULT_TIMEOUT_MS),\n })\n } catch (cause) {\n throw transportFailure(cause)\n }\n\n const text = await response.text()\n let parsed: unknown\n try {\n parsed = JSON.parse(text)\n } catch (cause) {\n throw unparsedFailure(response.status, cause)\n }\n\n const success = SuccessBody.safeParse(parsed)\n if (success.success) return decideSuccess(success.data, check)\n const failure = FailureBody.safeParse(parsed)\n if (failure.success) return decideFailure(failure.data['error-codes'], response.status)\n throw unparsedFailure(response.status, { status: response.status })\n}\n\nfunction decideSuccess(body: SuccessBody, check: TurnstileCheck): TurnstileVerdict {\n // A testing secret omits action. Compare only an action the response names.\n if (\n check.expectedAction !== undefined &&\n body.action !== undefined &&\n body.action !== check.expectedAction\n ) {\n return { _tag: 'Rejected', errorCodes: ['action-mismatch'] }\n }\n // A testing secret reports example.com, so the allowlist does not apply to it.\n const testingKey = body.metadata?.result_with_testing_key === true\n if (\n !testingKey &&\n check.expectedHostnames !== undefined &&\n !check.expectedHostnames.includes(body.hostname)\n ) {\n return { _tag: 'Rejected', errorCodes: ['hostname-mismatch'] }\n }\n if (body.action === undefined) {\n return { _tag: 'Accepted', hostname: body.hostname, challengeTs: body.challenge_ts }\n }\n return {\n _tag: 'Accepted',\n hostname: body.hostname,\n challengeTs: body.challenge_ts,\n action: body.action,\n }\n}\n\nfunction decideFailure(errorCodes: readonly string[], status: number): TurnstileVerdict {\n if (errorCodes.some((code) => CONFIGURATION_CODES.has(code))) {\n throw verificationFailure('terminal', errorCodes, status)\n }\n if (errorCodes.includes('internal-error') || status === 429 || status >= 500) {\n throw new SiteverifyRetry(errorCodes, { status })\n }\n return { _tag: 'Rejected', errorCodes }\n}\n\nfunction transportFailure(cause: unknown): Error {\n const classification = classifyBoundaryError({\n cause,\n classifyBoundary: () => undefined,\n includeCloudflare: false,\n })\n if (classification === 'transient') return new SiteverifyRetry([], cause)\n return new TurnstileVerificationError({ classification, errorCodes: [], cause })\n}\n\nfunction unparsedFailure(status: number, cause: unknown): Error {\n if (status === 429 || status >= 500) return new SiteverifyRetry([], cause)\n return verificationFailure('terminal', [], status)\n}\n\nfunction verificationFailure(\n classification: FailureClassification,\n errorCodes: readonly string[],\n status: number,\n): TurnstileVerificationError {\n return new TurnstileVerificationError({\n classification,\n errorCodes,\n cause: { status },\n })\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { _ as isTransientCloudflareRuntimeError, g as isCloudflareRetryableError, h as isCloudflareOverloadedError, nt as AppError, v as isAbortError, y as isTransientTransportError } from "./core-BiN6S_cm.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
//#region src/infrastructure/cloudflare/durable-object-errors.ts
|
|
5
|
+
/** Stable tag for a failed Durable Object call. */
|
|
6
|
+
const DURABLE_OBJECT_CALL_ERROR = "DurableObjectCallError";
|
|
7
|
+
const RemoteClassifiedErrorSchema = z.object({
|
|
8
|
+
_tag: z.string(),
|
|
9
|
+
classification: z.enum([
|
|
10
|
+
"transient",
|
|
11
|
+
"terminal",
|
|
12
|
+
"unknown"
|
|
13
|
+
]),
|
|
14
|
+
remote: z.literal(true)
|
|
15
|
+
});
|
|
16
|
+
const RemoteCallErrorSchema = RemoteClassifiedErrorSchema.extend({
|
|
17
|
+
_tag: z.literal(DURABLE_OBJECT_CALL_ERROR),
|
|
18
|
+
cause: z.unknown()
|
|
19
|
+
});
|
|
20
|
+
/** Recognize a judged local or remote error without admitting its tag to the public error table. */
|
|
21
|
+
function isClassifiedDurableObjectError(cause) {
|
|
22
|
+
return cause instanceof AppError || cause instanceof Error && RemoteClassifiedErrorSchema.safeParse(cause).success;
|
|
23
|
+
}
|
|
24
|
+
/** Classify the error itself. Its cause remains diagnostic evidence, not a second retry decision. */
|
|
25
|
+
function classifyDurableObjectError(cause) {
|
|
26
|
+
if (isAbortError(cause)) return "terminal";
|
|
27
|
+
if (isClassifiedDurableObjectError(cause)) return cause.classification;
|
|
28
|
+
if (isCloudflareOverloadedError(cause)) return "terminal";
|
|
29
|
+
if (isCloudflareRetryableError(cause) || isTransientCloudflareRuntimeError(cause) || isTransientTransportError(cause)) return "transient";
|
|
30
|
+
return "unknown";
|
|
31
|
+
}
|
|
32
|
+
/** Identify a local call error or its validated Workers RPC representation. */
|
|
33
|
+
function isDurableObjectCallError(cause) {
|
|
34
|
+
return cause instanceof DurableObjectCallError || cause instanceof Error && RemoteCallErrorSchema.safeParse(cause).success;
|
|
35
|
+
}
|
|
36
|
+
/** A call failed after retries stopped. Classification does not establish repeat safety. */
|
|
37
|
+
var DurableObjectCallError = class extends AppError {
|
|
38
|
+
_tag = DURABLE_OBJECT_CALL_ERROR;
|
|
39
|
+
classification;
|
|
40
|
+
constructor(args) {
|
|
41
|
+
super("Durable Object call failed", { cause: args.cause });
|
|
42
|
+
this.classification = classifyDurableObjectError(args.cause);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
47
|
+
export { isDurableObjectCallError as a, isClassifiedDurableObjectError as i, DurableObjectCallError as n, classifyDurableObjectError as r, DURABLE_OBJECT_CALL_ERROR as t };
|
|
48
|
+
//# sourceMappingURL=durable-object-errors-DkMFXQnK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"durable-object-errors-DkMFXQnK.js","names":[],"sources":["../src/infrastructure/cloudflare/durable-object-errors.ts"],"sourcesContent":["import { z } from 'zod'\n\nimport {\n AppError,\n isAbortError,\n isCloudflareOverloadedError,\n isCloudflareRetryableError,\n isTransientCloudflareRuntimeError,\n isTransientTransportError,\n type FailureClassification,\n} from '@/core'\n\n/** Stable tag for a failed Durable Object call. */\nexport const DURABLE_OBJECT_CALL_ERROR = 'DurableObjectCallError'\n\nconst RemoteClassifiedErrorSchema = z.object({\n _tag: z.string(),\n classification: z.enum(['transient', 'terminal', 'unknown']),\n remote: z.literal(true),\n})\n\nconst RemoteCallErrorSchema = RemoteClassifiedErrorSchema.extend({\n _tag: z.literal(DURABLE_OBJECT_CALL_ERROR),\n cause: z.unknown(),\n})\n\n/** Recognize a judged local or remote error without admitting its tag to the public error table. */\nexport function isClassifiedDurableObjectError(\n cause: unknown,\n): cause is AppError | (Error & z.output<typeof RemoteClassifiedErrorSchema>) {\n return (\n cause instanceof AppError ||\n (cause instanceof Error && RemoteClassifiedErrorSchema.safeParse(cause).success)\n )\n}\n\n/** Classify the error itself. Its cause remains diagnostic evidence, not a second retry decision. */\nexport function classifyDurableObjectError(cause: unknown): FailureClassification {\n if (isAbortError(cause)) return 'terminal'\n if (isClassifiedDurableObjectError(cause)) return cause.classification\n if (isCloudflareOverloadedError(cause)) return 'terminal'\n if (\n isCloudflareRetryableError(cause) ||\n isTransientCloudflareRuntimeError(cause) ||\n isTransientTransportError(cause)\n )\n return 'transient'\n return 'unknown'\n}\n\n/** Identify a local call error or its validated Workers RPC representation. */\nexport function isDurableObjectCallError(\n cause: unknown,\n): cause is DurableObjectCallError | (Error & z.output<typeof RemoteCallErrorSchema>) {\n return (\n cause instanceof DurableObjectCallError ||\n (cause instanceof Error && RemoteCallErrorSchema.safeParse(cause).success)\n )\n}\n\n/** A call failed after retries stopped. Classification does not establish repeat safety. */\nexport class DurableObjectCallError extends AppError {\n readonly _tag = DURABLE_OBJECT_CALL_ERROR\n readonly classification: FailureClassification\n\n constructor(args: { readonly cause: unknown }) {\n super('Durable Object call failed', { cause: args.cause })\n this.classification = classifyDurableObjectError(args.cause)\n }\n}\n"],"mappings":";;;;;AAaA,MAAa,4BAA4B;AAEzC,MAAM,8BAA8B,EAAE,OAAO;CAC3C,MAAM,EAAE,OAAO;CACf,gBAAgB,EAAE,KAAK;EAAC;EAAa;EAAY;CAAS,CAAC;CAC3D,QAAQ,EAAE,QAAQ,IAAI;AACxB,CAAC;AAED,MAAM,wBAAwB,4BAA4B,OAAO;CAC/D,MAAM,EAAE,QAAQ,yBAAyB;CACzC,OAAO,EAAE,QAAQ;AACnB,CAAC;;AAGD,SAAgB,+BACd,OAC4E;CAC5E,OACE,iBAAiB,YAChB,iBAAiB,SAAS,4BAA4B,UAAU,KAAK,CAAC,CAAC;AAE5E;;AAGA,SAAgB,2BAA2B,OAAuC;CAChF,IAAI,aAAa,KAAK,GAAG,OAAO;CAChC,IAAI,+BAA+B,KAAK,GAAG,OAAO,MAAM;CACxD,IAAI,4BAA4B,KAAK,GAAG,OAAO;CAC/C,IACE,2BAA2B,KAAK,KAChC,kCAAkC,KAAK,KACvC,0BAA0B,KAAK,GAE/B,OAAO;CACT,OAAO;AACT;;AAGA,SAAgB,yBACd,OACoF;CACpF,OACE,iBAAiB,0BAChB,iBAAiB,SAAS,sBAAsB,UAAU,KAAK,CAAC,CAAC;AAEtE;;AAGA,IAAa,yBAAb,cAA4C,SAAS;CACnD,AAAS,OAAO;CAChB,AAAS;CAET,YAAY,MAAmC;EAC7C,MAAM,8BAA8B,EAAE,OAAO,KAAK,MAAM,CAAC;EACzD,KAAK,iBAAiB,2BAA2B,KAAK,KAAK;CAC7D;AACF"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { g as isCloudflareRetryableError, h as isCloudflareOverloadedError, nt as AppError, y as isTransientTransportError } from "./core-BiN6S_cm.js";
|
|
2
|
+
|
|
3
|
+
//#region src/infrastructure/cloudflare/kv-errors.ts
|
|
4
|
+
/** Extract Typist's verified KV message formats, including nested causes. */
|
|
5
|
+
function extractKVMeta(cause) {
|
|
6
|
+
const message = getDeepErrorMessage(cause);
|
|
7
|
+
if (!message) return void 0;
|
|
8
|
+
const statusMatch = message.match(/\bKV\s+(GET|PUT|DELETE|LIST)\s+failed:\s+(\d{3})\b/);
|
|
9
|
+
const operation = statusMatch?.[1];
|
|
10
|
+
const status = statusMatch ? Number(statusMatch[2]) : void 0;
|
|
11
|
+
if (/Key name cannot be empty/i.test(message)) return {
|
|
12
|
+
kind: "invalid_key",
|
|
13
|
+
message,
|
|
14
|
+
status,
|
|
15
|
+
operation
|
|
16
|
+
};
|
|
17
|
+
if (/exceeds key length limit/i.test(message)) return {
|
|
18
|
+
kind: "key_too_large",
|
|
19
|
+
message,
|
|
20
|
+
status,
|
|
21
|
+
operation
|
|
22
|
+
};
|
|
23
|
+
if (/Invalid expiration_ttl/i.test(message)) return {
|
|
24
|
+
kind: "invalid_expiration_ttl",
|
|
25
|
+
message,
|
|
26
|
+
status,
|
|
27
|
+
operation
|
|
28
|
+
};
|
|
29
|
+
if (/Invalid expiration of/i.test(message)) return {
|
|
30
|
+
kind: "invalid_expiration",
|
|
31
|
+
message,
|
|
32
|
+
status,
|
|
33
|
+
operation
|
|
34
|
+
};
|
|
35
|
+
if (/Invalid cache_ttl/i.test(message)) return {
|
|
36
|
+
kind: "invalid_cache_ttl",
|
|
37
|
+
message,
|
|
38
|
+
status,
|
|
39
|
+
operation
|
|
40
|
+
};
|
|
41
|
+
if (/Invalid key_count_limit/i.test(message)) return {
|
|
42
|
+
kind: "invalid_list_limit",
|
|
43
|
+
message,
|
|
44
|
+
status,
|
|
45
|
+
operation
|
|
46
|
+
};
|
|
47
|
+
if (/value.*(too large|exceeds)|exceeds.*value/i.test(message)) return {
|
|
48
|
+
kind: "value_too_large",
|
|
49
|
+
message,
|
|
50
|
+
status,
|
|
51
|
+
operation
|
|
52
|
+
};
|
|
53
|
+
if (status === 429) return {
|
|
54
|
+
kind: "rate_limited",
|
|
55
|
+
message,
|
|
56
|
+
status,
|
|
57
|
+
operation
|
|
58
|
+
};
|
|
59
|
+
if (status !== void 0 && status >= 500) return {
|
|
60
|
+
kind: "server_error",
|
|
61
|
+
message,
|
|
62
|
+
status,
|
|
63
|
+
operation
|
|
64
|
+
};
|
|
65
|
+
if (status !== void 0 && status >= 400) return {
|
|
66
|
+
kind: "client_error",
|
|
67
|
+
message,
|
|
68
|
+
status,
|
|
69
|
+
operation
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** Identify recognized KV failures that another attempt cannot resolve. */
|
|
73
|
+
function isTerminalKVError(cause) {
|
|
74
|
+
const meta = extractKVMeta(cause);
|
|
75
|
+
if (!meta) return false;
|
|
76
|
+
if (meta.kind === "rate_limited" || meta.kind === "server_error") return false;
|
|
77
|
+
if (meta.status === 408) return false;
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
/** Identify a PUT rate limit that requires at least one second before retrying. */
|
|
81
|
+
function isKVPutRateLimitError(cause) {
|
|
82
|
+
const meta = extractKVMeta(cause);
|
|
83
|
+
return meta?.operation === "PUT" && meta.kind === "rate_limited";
|
|
84
|
+
}
|
|
85
|
+
/** Identify PUT rate limits, server failures, and request timeouts. */
|
|
86
|
+
function isTransientKVError(cause) {
|
|
87
|
+
const meta = extractKVMeta(cause);
|
|
88
|
+
if (!meta) return false;
|
|
89
|
+
return isKVPutRateLimitError(cause) || meta.kind === "server_error" || meta.status === 408;
|
|
90
|
+
}
|
|
91
|
+
/** Apply Typist's KV, overload, platform, and transport checks in their original order. */
|
|
92
|
+
function isRetryableKVError(cause) {
|
|
93
|
+
if (isTerminalKVError(cause)) return false;
|
|
94
|
+
if (isTransientKVError(cause)) return true;
|
|
95
|
+
if (isCloudflareOverloadedError(cause)) return false;
|
|
96
|
+
return isCloudflareRetryableError(cause) || isTransientTransportError(cause);
|
|
97
|
+
}
|
|
98
|
+
/** Return the existing retry decision and minimum delay in milliseconds. */
|
|
99
|
+
function decideKVRetry(cause) {
|
|
100
|
+
if (!isRetryableKVError(cause)) return { retry: false };
|
|
101
|
+
return {
|
|
102
|
+
retry: true,
|
|
103
|
+
retryAfterMs: isKVPutRateLimitError(cause) ? 1e3 : 0
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/** A final KV failure with Typist's diagnostics and Better Ship's error classification. */
|
|
107
|
+
var KVError = class extends AppError {
|
|
108
|
+
_tag = "KVError";
|
|
109
|
+
classification;
|
|
110
|
+
retryable;
|
|
111
|
+
operation;
|
|
112
|
+
key;
|
|
113
|
+
cause;
|
|
114
|
+
kv;
|
|
115
|
+
context;
|
|
116
|
+
constructor(args) {
|
|
117
|
+
const kv = extractKVMeta(args.cause);
|
|
118
|
+
const causeMessage = getDeepErrorMessage(args.cause) || String(args.cause);
|
|
119
|
+
const keyContext = args.key === void 0 ? "" : ` for key "${args.key}"`;
|
|
120
|
+
super(`KV ${args.operation} failed${keyContext}: ${causeMessage}`, { cause: args.cause });
|
|
121
|
+
this.operation = args.operation;
|
|
122
|
+
this.key = args.key;
|
|
123
|
+
this.cause = args.cause;
|
|
124
|
+
this.kv = kv;
|
|
125
|
+
this.context = {
|
|
126
|
+
operation: args.operation,
|
|
127
|
+
key: args.key,
|
|
128
|
+
kv
|
|
129
|
+
};
|
|
130
|
+
this.retryable = isRetryableKVError(args.cause);
|
|
131
|
+
this.classification = isTerminalKVError(args.cause) ? "terminal" : this.retryable ? "transient" : "unknown";
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
function getDeepErrorMessage(cause) {
|
|
135
|
+
if (typeof cause === "string") return cause;
|
|
136
|
+
if (cause === null || cause === void 0) return "";
|
|
137
|
+
if (typeof cause === "number" || typeof cause === "boolean" || typeof cause === "bigint") return String(cause);
|
|
138
|
+
if (typeof cause !== "object") return "";
|
|
139
|
+
return `${"message" in cause && typeof cause.message === "string" ? cause.message : ""} ${"cause" in cause ? getDeepErrorMessage(cause.cause) : ""}`.trim();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
//#endregion
|
|
143
|
+
export { isRetryableKVError as a, isKVPutRateLimitError as i, decideKVRetry as n, isTerminalKVError as o, extractKVMeta as r, isTransientKVError as s, KVError as t };
|
|
144
|
+
//# sourceMappingURL=kv-errors-BUTpFy0t.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kv-errors-BUTpFy0t.js","names":[],"sources":["../src/infrastructure/cloudflare/kv-errors.ts"],"sourcesContent":["import {\n AppError,\n isCloudflareOverloadedError,\n isCloudflareRetryableError,\n isTransientTransportError,\n type FailureClassification,\n} from '@/core'\n\n/** KV operations recognized in binding error messages. */\nexport type KVOperation = 'get' | 'put' | 'delete' | 'list'\n\n/** KV failure kinds retained from Typist's binding tests. */\nexport type KVErrorKind =\n | 'invalid_key'\n | 'key_too_large'\n | 'invalid_expiration_ttl'\n | 'invalid_expiration'\n | 'invalid_cache_ttl'\n | 'invalid_list_limit'\n | 'value_too_large'\n | 'client_error'\n | 'rate_limited'\n | 'server_error'\n\n/** Evidence extracted from a KV error and its causes. */\nexport interface KVErrorMeta {\n kind: KVErrorKind\n message: string\n status?: number | undefined\n operation?: Uppercase<KVOperation> | undefined\n}\n\n/** Extract Typist's verified KV message formats, including nested causes. */\nexport function extractKVMeta(cause: unknown): KVErrorMeta | undefined {\n const message = getDeepErrorMessage(cause)\n if (!message) return undefined\n const statusMatch = message.match(/\\bKV\\s+(GET|PUT|DELETE|LIST)\\s+failed:\\s+(\\d{3})\\b/)\n // SAFETY: The regular expression permits only uppercase KV operation names in this capture.\n const operation = statusMatch?.[1] as Uppercase<KVOperation> | undefined\n const status = statusMatch ? Number(statusMatch[2]) : undefined\n\n if (/Key name cannot be empty/i.test(message)) {\n return { kind: 'invalid_key', message, status, operation }\n }\n if (/exceeds key length limit/i.test(message)) {\n return { kind: 'key_too_large', message, status, operation }\n }\n if (/Invalid expiration_ttl/i.test(message)) {\n return { kind: 'invalid_expiration_ttl', message, status, operation }\n }\n if (/Invalid expiration of/i.test(message)) {\n return { kind: 'invalid_expiration', message, status, operation }\n }\n if (/Invalid cache_ttl/i.test(message)) {\n return { kind: 'invalid_cache_ttl', message, status, operation }\n }\n if (/Invalid key_count_limit/i.test(message)) {\n return { kind: 'invalid_list_limit', message, status, operation }\n }\n if (/value.*(too large|exceeds)|exceeds.*value/i.test(message)) {\n return { kind: 'value_too_large', message, status, operation }\n }\n if (status === 429) return { kind: 'rate_limited', message, status, operation }\n if (status !== undefined && status >= 500) {\n return { kind: 'server_error', message, status, operation }\n }\n if (status !== undefined && status >= 400) {\n return { kind: 'client_error', message, status, operation }\n }\n return undefined\n}\n\n/** Identify recognized KV failures that another attempt cannot resolve. */\nexport function isTerminalKVError(cause: unknown): boolean {\n const meta = extractKVMeta(cause)\n if (!meta) return false\n if (meta.kind === 'rate_limited' || meta.kind === 'server_error') return false\n if (meta.status === 408) return false\n return true\n}\n\n/** Identify a PUT rate limit that requires at least one second before retrying. */\nexport function isKVPutRateLimitError(cause: unknown): boolean {\n const meta = extractKVMeta(cause)\n return meta?.operation === 'PUT' && meta.kind === 'rate_limited'\n}\n\n/** Identify PUT rate limits, server failures, and request timeouts. */\nexport function isTransientKVError(cause: unknown): boolean {\n const meta = extractKVMeta(cause)\n if (!meta) return false\n return isKVPutRateLimitError(cause) || meta.kind === 'server_error' || meta.status === 408\n}\n\n/** Apply Typist's KV, overload, platform, and transport checks in their original order. */\nexport function isRetryableKVError(cause: unknown): boolean {\n if (isTerminalKVError(cause)) return false\n if (isTransientKVError(cause)) return true\n if (isCloudflareOverloadedError(cause)) return false\n return isCloudflareRetryableError(cause) || isTransientTransportError(cause)\n}\n\n/** Return the existing retry decision and minimum delay in milliseconds. */\nexport function decideKVRetry(\n cause: unknown,\n): { retry: false } | { retry: true; retryAfterMs: number } {\n if (!isRetryableKVError(cause)) return { retry: false }\n return { retry: true, retryAfterMs: isKVPutRateLimitError(cause) ? 1_000 : 0 }\n}\n\n/** A final KV failure with Typist's diagnostics and Better Ship's error classification. */\nexport class KVError extends AppError {\n readonly _tag = 'KVError'\n readonly classification: FailureClassification\n retryable: boolean\n readonly operation: KVOperation\n readonly key?: string | undefined\n override readonly cause: unknown\n readonly kv?: KVErrorMeta | undefined\n readonly context: {\n operation: KVOperation\n key?: string | undefined\n kv?: KVErrorMeta | undefined\n }\n\n constructor(args: { operation: KVOperation; key?: string | undefined; cause: unknown }) {\n const kv = extractKVMeta(args.cause)\n const causeMessage = getDeepErrorMessage(args.cause) || String(args.cause)\n const keyContext = args.key === undefined ? '' : ` for key \"${args.key}\"`\n super(`KV ${args.operation} failed${keyContext}: ${causeMessage}`, { cause: args.cause })\n this.operation = args.operation\n this.key = args.key\n this.cause = args.cause\n this.kv = kv\n this.context = { operation: args.operation, key: args.key, kv }\n this.retryable = isRetryableKVError(args.cause)\n this.classification = isTerminalKVError(args.cause)\n ? 'terminal'\n : this.retryable\n ? 'transient'\n : 'unknown'\n }\n}\n\n// oxlint-disable anti-slop/no-runtime-typeof -- Preserve Typist's primitive messages and nested causes at the raw binding boundary.\nfunction getDeepErrorMessage(cause: unknown): string {\n if (typeof cause === 'string') return cause\n if (cause === null || cause === undefined) return ''\n if (typeof cause === 'number' || typeof cause === 'boolean' || typeof cause === 'bigint') {\n return String(cause)\n }\n if (typeof cause !== 'object') return ''\n const message = 'message' in cause && typeof cause.message === 'string' ? cause.message : ''\n const causeMessage = 'cause' in cause ? getDeepErrorMessage(cause.cause) : ''\n return `${message} ${causeMessage}`.trim()\n}\n// oxlint-enable anti-slop/no-runtime-typeof\n"],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/infrastructure/posthog/metrics.ts
|
|
2
|
+
/** PostHog Metrics with Worker lifetime management, ported from Typist's metrics-service.ts. */
|
|
3
|
+
var PostHogMetricsService = class {
|
|
4
|
+
client;
|
|
5
|
+
waitUntil;
|
|
6
|
+
/** Use the analytics client's metrics; the runtime owns each flush promise. */
|
|
7
|
+
constructor(client, waitUntil) {
|
|
8
|
+
this.client = client;
|
|
9
|
+
this.waitUntil = waitUntil;
|
|
10
|
+
}
|
|
11
|
+
/** Record a total and schedule its flush. */
|
|
12
|
+
count(name, value = 1, options) {
|
|
13
|
+
this.client.metrics.count(name, value, options);
|
|
14
|
+
this.waitUntil(this.client.metrics.flush());
|
|
15
|
+
}
|
|
16
|
+
/** Record a current value and schedule its flush. */
|
|
17
|
+
gauge(name, value, options) {
|
|
18
|
+
this.client.metrics.gauge(name, value, options);
|
|
19
|
+
this.waitUntil(this.client.metrics.flush());
|
|
20
|
+
}
|
|
21
|
+
/** Record an observation and schedule its flush. */
|
|
22
|
+
histogram(name, value, options) {
|
|
23
|
+
this.client.metrics.histogram(name, value, options);
|
|
24
|
+
this.waitUntil(this.client.metrics.flush());
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
export { PostHogMetricsService as t };
|
|
30
|
+
//# sourceMappingURL=metrics-CWv37yf9.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics-CWv37yf9.js","names":[],"sources":["../src/infrastructure/posthog/metrics.ts"],"sourcesContent":["import type { PostHog } from 'posthog-node'\n\nimport type { MetricOptions, MetricsService } from '@/application'\n\n/** PostHog Metrics with Worker lifetime management, ported from Typist's metrics-service.ts. */\nexport class PostHogMetricsService implements MetricsService {\n /** Use the analytics client's metrics; the runtime owns each flush promise. */\n constructor(\n private readonly client: Pick<PostHog, 'metrics'>,\n private readonly waitUntil: (promise: Promise<unknown>) => void,\n ) {}\n\n /** Record a total and schedule its flush. */\n count(name: string, value = 1, options?: MetricOptions): void {\n this.client.metrics.count(name, value, options)\n this.waitUntil(this.client.metrics.flush())\n }\n\n /** Record a current value and schedule its flush. */\n gauge(name: string, value: number, options?: MetricOptions): void {\n this.client.metrics.gauge(name, value, options)\n this.waitUntil(this.client.metrics.flush())\n }\n\n /** Record an observation and schedule its flush. */\n histogram(name: string, value: number, options?: MetricOptions): void {\n this.client.metrics.histogram(name, value, options)\n this.waitUntil(this.client.metrics.flush())\n }\n}\n"],"mappings":";;AAKA,IAAa,wBAAb,MAA6D;CAGxC;CACA;;CAFnB,YACE,AAAiB,QACjB,AAAiB,WACjB;EAFiB;EACA;CAChB;;CAGH,MAAM,MAAc,QAAQ,GAAG,SAA+B;EAC5D,KAAK,OAAO,QAAQ,MAAM,MAAM,OAAO,OAAO;EAC9C,KAAK,UAAU,KAAK,OAAO,QAAQ,MAAM,CAAC;CAC5C;;CAGA,MAAM,MAAc,OAAe,SAA+B;EAChE,KAAK,OAAO,QAAQ,MAAM,MAAM,OAAO,OAAO;EAC9C,KAAK,UAAU,KAAK,OAAO,QAAQ,MAAM,CAAC;CAC5C;;CAGA,UAAU,MAAc,OAAe,SAA+B;EACpE,KAAK,OAAO,QAAQ,UAAU,MAAM,OAAO,OAAO;EAClD,KAAK,UAAU,KAAK,OAAO,QAAQ,MAAM,CAAC;CAC5C;AACF"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { X as MetricOptions, Z as MetricsService } from "./index-lS_g1olK.js";
|
|
2
|
+
import { PostHog } from "posthog-node";
|
|
3
|
+
//#region src/infrastructure/posthog/metrics.d.ts
|
|
4
|
+
/** PostHog Metrics with Worker lifetime management, ported from Typist's metrics-service.ts. */
|
|
5
|
+
declare class PostHogMetricsService implements MetricsService {
|
|
6
|
+
private readonly client;
|
|
7
|
+
private readonly waitUntil;
|
|
8
|
+
/** Use the analytics client's metrics; the runtime owns each flush promise. */
|
|
9
|
+
constructor(client: Pick<PostHog, 'metrics'>, waitUntil: (promise: Promise<unknown>) => void);
|
|
10
|
+
/** Record a total and schedule its flush. */
|
|
11
|
+
count(name: string, value?: number, options?: MetricOptions): void;
|
|
12
|
+
/** Record a current value and schedule its flush. */
|
|
13
|
+
gauge(name: string, value: number, options?: MetricOptions): void;
|
|
14
|
+
/** Record an observation and schedule its flush. */
|
|
15
|
+
histogram(name: string, value: number, options?: MetricOptions): void;
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { PostHogMetricsService as t };
|
|
19
|
+
//# sourceMappingURL=metrics-cv9P5nvk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics-cv9P5nvk.d.ts","names":[],"sources":["../src/infrastructure/posthog/metrics.ts"],"mappings":";;;;cAKa,iCAAiC;mBAGzB;mBACA;;EAFnB,YACmB,QAAQ,KAAK,qBACb,YAAY,SAAS;;EAIxC,MAAM,cAAc,gBAAW,UAAU;;EAMzC,MAAM,cAAc,eAAe,UAAU;;EAM7C,UAAU,cAAc,eAAe,UAAU"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import Stripe from "stripe";
|
|
2
|
+
//#region src/infrastructure/stripe/payment-service.d.ts
|
|
3
|
+
/** Supply the same key and parameters when repeating one durable payment attempt. */
|
|
4
|
+
type PaymentRequestOptions = Stripe.RequestOptions & Required<Pick<Stripe.RequestOptions, 'idempotencyKey'>>;
|
|
5
|
+
/** Native Checkout parameters; the service selects payment or setup mode. */
|
|
6
|
+
type CheckoutInput = Omit<Stripe.Checkout.SessionCreateParams, 'mode'>;
|
|
7
|
+
/** A saved customer method; the service confirms the payment off-session. */
|
|
8
|
+
type SavedMethodCharge = Omit<Stripe.PaymentIntentCreateParams, 'confirm' | 'off_session'> & Required<Pick<Stripe.PaymentIntentCreateParams, 'customer' | 'payment_method'>>;
|
|
9
|
+
/** Native customer response, including Stripe request metadata. */
|
|
10
|
+
type CustomerResult = Awaited<ReturnType<Stripe['customers']['create']>>;
|
|
11
|
+
/** Native Checkout response. Its URL can be null; this does not confirm payment. */
|
|
12
|
+
type CheckoutResult = Awaited<ReturnType<Stripe['checkout']['sessions']['create']>>;
|
|
13
|
+
/** Native PaymentIntent response. Applications decide what each status permits. */
|
|
14
|
+
type PaymentResult = Awaited<ReturnType<Stripe['paymentIntents']['create']>>;
|
|
15
|
+
/**
|
|
16
|
+
* Stripe payment operations, with native types and stable keys for mutations.
|
|
17
|
+
* Rejects with PaymentServiceError for Stripe failures; non-Stripe failures propagate unchanged.
|
|
18
|
+
*/
|
|
19
|
+
interface IPaymentService {
|
|
20
|
+
/** Create a customer; the application owns persistence and reuse of the customer link. */
|
|
21
|
+
createCustomer(input: Stripe.CustomerCreateParams, options: PaymentRequestOptions): Promise<CustomerResult>;
|
|
22
|
+
/** Create one-time payment checkout; application fulfillment requires verified payment confirmation. */
|
|
23
|
+
createCheckout(input: CheckoutInput, options: PaymentRequestOptions): Promise<CheckoutResult>;
|
|
24
|
+
/** Create checkout in setup mode. The application records consent and verifies the saved method's owner. */
|
|
25
|
+
setupPaymentMethod(input: CheckoutInput, options: PaymentRequestOptions): Promise<CheckoutResult>;
|
|
26
|
+
/** Confirm a saved method off-session. PaymentServiceError retains card errors in cause. */
|
|
27
|
+
chargeSavedMethod(input: SavedMethodCharge, options: PaymentRequestOptions): Promise<PaymentResult>;
|
|
28
|
+
/** Retrieve an existing payment without starting another payment attempt. */
|
|
29
|
+
getPayment(paymentId: string, params?: Stripe.PaymentIntentRetrieveParams, options?: Stripe.RequestOptions): Promise<PaymentResult>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Ported from Typist's StripeClient request boundary; the native SDK now owns retries.
|
|
33
|
+
* The injected client owns retry and timeout defaults. Native request options can override them.
|
|
34
|
+
*/
|
|
35
|
+
declare class StripePaymentService implements IPaymentService {
|
|
36
|
+
private readonly stripe;
|
|
37
|
+
/** Inject a native SDK instance; its transport, credentials, and API version remain caller-owned. */
|
|
38
|
+
constructor(stripe: Stripe);
|
|
39
|
+
/** Create a customer with a stable application key and preserve the native response. */
|
|
40
|
+
createCustomer(input: Stripe.CustomerCreateParams, options: PaymentRequestOptions): Promise<CustomerResult>;
|
|
41
|
+
/** Force payment mode after caller parameters; return the native Checkout Session. */
|
|
42
|
+
createCheckout(input: CheckoutInput, options: PaymentRequestOptions): Promise<CheckoutResult>;
|
|
43
|
+
/** Force setup mode after caller parameters; this operation does not charge the customer. */
|
|
44
|
+
setupPaymentMethod(input: CheckoutInput, options: PaymentRequestOptions): Promise<CheckoutResult>;
|
|
45
|
+
/** Force confirmation and off-session use; retain native card failures in PaymentServiceError.cause. */
|
|
46
|
+
chargeSavedMethod(input: SavedMethodCharge, options: PaymentRequestOptions): Promise<PaymentResult>;
|
|
47
|
+
/** Preserve every native status and response field when reconciling an existing payment. */
|
|
48
|
+
getPayment(paymentId: string, params?: Stripe.PaymentIntentRetrieveParams, options?: Stripe.RequestOptions): Promise<PaymentResult>;
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
51
|
+
export { PaymentRequestOptions as a, StripePaymentService as c, IPaymentService as i, CheckoutResult as n, PaymentResult as o, CustomerResult as r, SavedMethodCharge as s, CheckoutInput as t };
|
|
52
|
+
//# sourceMappingURL=payment-service-DUYd3Itt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payment-service-DUYd3Itt.d.ts","names":[],"sources":["../src/infrastructure/stripe/payment-service.ts"],"mappings":";;;KAKY,wBAAwB,OAAO,iBACzC,SAAS,KAAK,OAAO;;KAGX,gBAAgB,KAAK,OAAO,SAAS;;KAGrC,oBAAoB,KAAK,OAAO,wDAC1C,SAAS,KAAK,OAAO;;KAGX,iBAAiB,QAAQ,WAAW;;KAGpC,iBAAiB,QAAQ,WAAW;;KAGpC,gBAAgB,QAAQ,WAAW;;;;;UAM9B;;EAEf,eACE,OAAO,OAAO,sBACd,SAAS,wBACR,QAAQ;;EAEX,eAAe,OAAO,eAAe,SAAS,wBAAwB,QAAQ;;EAE9E,mBAAmB,OAAO,eAAe,SAAS,wBAAwB,QAAQ;;EAElF,kBACE,OAAO,mBACP,SAAS,wBACR,QAAQ;;EAEX,WACE,mBACA,SAAS,OAAO,6BAChB,UAAU,OAAO,iBAChB,QAAQ;;;;;;cAOA,gCAAgC;mBAEd;;EAA7B,YAA6B,QAAQ;;EAGrC,eACE,OAAO,OAAO,sBACd,SAAS,wBACR,QAAQ;;EAKX,eAAe,OAAO,eAAe,SAAS,wBAAwB,QAAQ;;EAO9E,mBACE,OAAO,eACP,SAAS,wBACR,QAAQ;;EAOX,kBACE,OAAO,mBACP,SAAS,wBACR,QAAQ;;EAOX,WACE,mBACA,SAAS,OAAO,6BAChB,UAAU,OAAO,iBAChB,QAAQ"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { nt as AppError } from "./core-BiN6S_cm.js";
|
|
2
|
+
import Stripe from "stripe";
|
|
3
|
+
|
|
4
|
+
//#region src/infrastructure/stripe/payment-service.error.ts
|
|
5
|
+
/** A final Stripe request failure. The caller logs once; native details remain in cause. */
|
|
6
|
+
var PaymentServiceError = class extends AppError {
|
|
7
|
+
operation;
|
|
8
|
+
_tag = "PaymentServiceError";
|
|
9
|
+
classification;
|
|
10
|
+
/** Classify the final SDK error without scheduling another payment attempt. */
|
|
11
|
+
constructor(operation, cause) {
|
|
12
|
+
super(`Stripe request failed: ${operation}`, { cause });
|
|
13
|
+
this.operation = operation;
|
|
14
|
+
this.classification = classifyStripeError(cause);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
function classifyStripeError(error) {
|
|
18
|
+
if (error.headers?.["stripe-should-retry"] === "false") return "terminal";
|
|
19
|
+
const status = error.statusCode;
|
|
20
|
+
if (status !== void 0 && status >= 400 && status < 500 && status !== 409 && status !== 429) return "terminal";
|
|
21
|
+
if (error instanceof Stripe.errors.StripeAuthenticationError || error instanceof Stripe.errors.StripePermissionError || error instanceof Stripe.errors.StripeInvalidRequestError || error instanceof Stripe.errors.StripeIdempotencyError || error instanceof Stripe.errors.StripeCardError) return "terminal";
|
|
22
|
+
if (error.headers?.["stripe-should-retry"] === "true") return "transient";
|
|
23
|
+
if (status === 409 || status === 429 || status !== void 0 && status >= 500) return "transient";
|
|
24
|
+
if (error instanceof Stripe.errors.StripeConnectionError || error instanceof Stripe.errors.StripeAPIError || error instanceof Stripe.errors.StripeRateLimitError) return "transient";
|
|
25
|
+
return "unknown";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
export { PaymentServiceError as t };
|
|
30
|
+
//# sourceMappingURL=payment-service.error-CqF7GkNp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payment-service.error-CqF7GkNp.js","names":[],"sources":["../src/infrastructure/stripe/payment-service.error.ts"],"sourcesContent":["import Stripe from 'stripe'\n\nimport { AppError, type FailureClassification } from '@/core'\n\n/** A final Stripe request failure. The caller logs once; native details remain in cause. */\nexport class PaymentServiceError extends AppError {\n readonly _tag = 'PaymentServiceError'\n readonly classification: FailureClassification\n\n /** Classify the final SDK error without scheduling another payment attempt. */\n constructor(\n readonly operation: string,\n cause: Stripe.errors.StripeError,\n ) {\n super(`Stripe request failed: ${operation}`, { cause })\n this.classification = classifyStripeError(cause)\n }\n}\n\nfunction classifyStripeError(error: Stripe.errors.StripeError): FailureClassification {\n if (error.headers?.['stripe-should-retry'] === 'false') return 'terminal'\n const status = error.statusCode\n if (status !== undefined && status >= 400 && status < 500 && status !== 409 && status !== 429)\n return 'terminal'\n if (\n error instanceof Stripe.errors.StripeAuthenticationError ||\n error instanceof Stripe.errors.StripePermissionError ||\n error instanceof Stripe.errors.StripeInvalidRequestError ||\n error instanceof Stripe.errors.StripeIdempotencyError ||\n error instanceof Stripe.errors.StripeCardError\n )\n return 'terminal'\n if (error.headers?.['stripe-should-retry'] === 'true') return 'transient'\n if (status === 409 || status === 429 || (status !== undefined && status >= 500))\n return 'transient'\n if (\n error instanceof Stripe.errors.StripeConnectionError ||\n error instanceof Stripe.errors.StripeAPIError ||\n error instanceof Stripe.errors.StripeRateLimitError\n )\n return 'transient'\n return 'unknown'\n}\n"],"mappings":";;;;;AAKA,IAAa,sBAAb,cAAyC,SAAS;CAMrC;CALX,AAAS,OAAO;CAChB,AAAS;;CAGT,YACE,AAAS,WACT,OACA;EACA,MAAM,0BAA0B,aAAa,EAAE,MAAM,CAAC;EAH7C;EAIT,KAAK,iBAAiB,oBAAoB,KAAK;CACjD;AACF;AAEA,SAAS,oBAAoB,OAAyD;CACpF,IAAI,MAAM,UAAU,2BAA2B,SAAS,OAAO;CAC/D,MAAM,SAAS,MAAM;CACrB,IAAI,WAAW,UAAa,UAAU,OAAO,SAAS,OAAO,WAAW,OAAO,WAAW,KACxF,OAAO;CACT,IACE,iBAAiB,OAAO,OAAO,6BAC/B,iBAAiB,OAAO,OAAO,yBAC/B,iBAAiB,OAAO,OAAO,6BAC/B,iBAAiB,OAAO,OAAO,0BAC/B,iBAAiB,OAAO,OAAO,iBAE/B,OAAO;CACT,IAAI,MAAM,UAAU,2BAA2B,QAAQ,OAAO;CAC9D,IAAI,WAAW,OAAO,WAAW,OAAQ,WAAW,UAAa,UAAU,KACzE,OAAO;CACT,IACE,iBAAiB,OAAO,OAAO,yBAC/B,iBAAiB,OAAO,OAAO,kBAC/B,iBAAiB,OAAO,OAAO,sBAE/B,OAAO;CACT,OAAO;AACT"}
|
|
@@ -1,18 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
//#region src/infrastructure/posthog/metrics.d.ts
|
|
4
|
-
/** PostHog Metrics with Worker lifetime management, ported from Typist's metrics-service.ts. */
|
|
5
|
-
export declare class PostHogMetricsService implements MetricsService {
|
|
6
|
-
private readonly client;
|
|
7
|
-
private readonly waitUntil;
|
|
8
|
-
/** Use the analytics client's metrics; the runtime owns each flush promise. */
|
|
9
|
-
constructor(client: Pick<PostHog, 'metrics'>, waitUntil: (promise: Promise<unknown>) => void);
|
|
10
|
-
/** Record a total and schedule its flush. */
|
|
11
|
-
count(name: string, value?: number, options?: MetricOptions): void;
|
|
12
|
-
/** Record a current value and schedule its flush. */
|
|
13
|
-
gauge(name: string, value: number, options?: MetricOptions): void;
|
|
14
|
-
/** Record an observation and schedule its flush. */
|
|
15
|
-
histogram(name: string, value: number, options?: MetricOptions): void;
|
|
16
|
-
}
|
|
17
|
-
//#endregion
|
|
18
|
-
//# sourceMappingURL=metrics.d.ts.map
|
|
1
|
+
import { t as PostHogMetricsService } from "../metrics-cv9P5nvk.js";
|
|
2
|
+
export { PostHogMetricsService };
|