better-ship 0.8.0 → 0.11.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-DYfcAy-Q.js → application-CIxo_BpK.js} +32 -3
- package/dist/application-CIxo_BpK.js.map +1 -0
- package/dist/application.d.ts +3 -3
- package/dist/application.js +2 -2
- package/dist/better-auth/apple-client-secret.d.ts +11 -4
- package/dist/better-auth/apple-client-secret.d.ts.map +1 -1
- package/dist/better-auth/apple-client-secret.js +39 -11
- package/dist/better-auth/apple-client-secret.js.map +1 -1
- package/dist/better-auth/auth-client.js +1 -1
- package/dist/better-auth/rate-limit.d.ts +21 -2
- package/dist/better-auth/rate-limit.d.ts.map +1 -1
- package/dist/better-auth/rate-limit.js +38 -2
- package/dist/better-auth/rate-limit.js.map +1 -1
- package/dist/better-auth.js +1 -1
- package/dist/cloudflare/auth-secondary-storage.d.ts +1 -1
- package/dist/cloudflare/auth-secondary-storage.js +1 -1
- package/dist/cloudflare/queue.d.ts +1 -1
- package/dist/cloudflare/queue.js +2 -2
- package/dist/cloudflare/scheduled.d.ts +36 -0
- package/dist/cloudflare/scheduled.d.ts.map +1 -0
- package/dist/cloudflare/scheduled.js +37 -0
- package/dist/cloudflare/scheduled.js.map +1 -0
- package/dist/cloudflare.d.ts +3 -3
- package/dist/cloudflare.js +3 -3
- package/dist/{core-DC_4pOrI.js → core-DG_cC8YT.js} +110 -2
- package/dist/{core-DC_4pOrI.js.map → core-DG_cC8YT.js.map} +1 -1
- package/dist/core.d.ts +2 -2
- package/dist/core.js +2 -2
- package/dist/http.d.ts +19 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +35 -0
- package/dist/http.js.map +1 -0
- package/dist/{index-6XMPJu7u.d.ts → index-Bl0K1RQW.d.ts} +90 -2
- package/dist/index-Bl0K1RQW.d.ts.map +1 -0
- package/dist/{index-_Cihfq1J.d.ts → index-DgDyeR5s.d.ts} +135 -55
- package/dist/index-DgDyeR5s.d.ts.map +1 -0
- package/dist/postgres.d.ts +3 -3
- package/dist/postgres.js +3 -3
- package/dist/resend.d.ts +73 -0
- package/dist/resend.d.ts.map +1 -0
- package/dist/resend.js +284 -0
- package/dist/resend.js.map +1 -0
- package/dist/tanstack/server.d.ts +8 -0
- package/dist/tanstack/server.d.ts.map +1 -0
- package/dist/tanstack/server.js +16 -0
- package/dist/tanstack/server.js.map +1 -0
- package/dist/tanstack.d.ts +18 -14
- package/dist/tanstack.d.ts.map +1 -1
- package/dist/tanstack.js +17 -23
- package/dist/tanstack.js.map +1 -1
- package/dist/{unit-of-work-context-BFCmU92Z.js → unit-of-work-context-BPE1In2f.js} +2 -2
- package/dist/{unit-of-work-context-BFCmU92Z.js.map → unit-of-work-context-BPE1In2f.js.map} +1 -1
- package/dist/{unit-of-work-context-BWTJfYPW.d.ts → unit-of-work-context-BzmnjlDa.d.ts} +3 -3
- package/dist/{unit-of-work-context-BWTJfYPW.d.ts.map → unit-of-work-context-BzmnjlDa.d.ts.map} +1 -1
- package/dist/webhook-verification-BQ9qZnVK.js +18 -0
- package/dist/webhook-verification-BQ9qZnVK.js.map +1 -0
- package/dist/webhook-verification-sZtwTI5n.d.ts +29 -0
- package/dist/webhook-verification-sZtwTI5n.d.ts.map +1 -0
- package/package.json +11 -6
- package/src/application/email-service.ts +55 -0
- package/src/application/index.ts +16 -0
- package/src/application/record-email-event.ts +53 -0
- package/src/core/email-address.ts +7 -0
- package/src/core/email-deliverability.ts +31 -0
- package/src/core/email-idempotency-key.ts +34 -0
- package/src/core/email-service.error.ts +51 -0
- package/src/core/index.ts +18 -0
- package/src/core/record-email-event.ts +23 -0
- package/src/infrastructure/better-auth/apple-client-secret.ts +41 -14
- package/src/infrastructure/better-auth/rate-limit.ts +44 -1
- package/src/infrastructure/cloudflare/scheduled/index.ts +8 -0
- package/src/infrastructure/cloudflare/scheduled/scheduled-handler.ts +62 -0
- package/src/infrastructure/http/index.ts +6 -0
- package/src/infrastructure/http/webhook-handler.ts +35 -0
- package/src/infrastructure/http/webhook-verification.ts +27 -0
- package/src/infrastructure/resend/email-event.ts +142 -0
- package/src/infrastructure/resend/index.ts +3 -0
- package/src/infrastructure/resend/resend-email-service.ts +125 -0
- package/src/infrastructure/resend/resend-webhook.ts +70 -0
- package/src/infrastructure/tanstack/auth.middleware.ts +14 -6
- package/src/infrastructure/tanstack/error.middleware.ts +7 -8
- package/src/infrastructure/tanstack/index.ts +0 -1
- package/src/infrastructure/tanstack/request-context.ts +11 -0
- package/src/infrastructure/tanstack/server.ts +7 -0
- package/src/infrastructure/tanstack/session-cookies.ts +6 -4
- package/dist/application-DYfcAy-Q.js.map +0 -1
- package/dist/index-6XMPJu7u.d.ts.map +0 -1
- package/dist/index-_Cihfq1J.d.ts.map +0 -1
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { StatusCodes } from 'http-status-codes'
|
|
2
|
+
|
|
3
|
+
import { createLogger, type Logger } from '@/core'
|
|
4
|
+
|
|
5
|
+
import { WebhookVerificationError, type WebhookVerifier } from './webhook-verification.ts'
|
|
6
|
+
|
|
7
|
+
/** Read raw webhook requests and acknowledge effects only after the callback completes. */
|
|
8
|
+
export class WebhookHandler<Event> {
|
|
9
|
+
/** Inject provider verification and an optional logger with the application's module name. */
|
|
10
|
+
constructor(
|
|
11
|
+
private readonly verifier: WebhookVerifier<Event>,
|
|
12
|
+
private readonly logger: Pick<Logger, 'warn'> = createLogger('webhook'),
|
|
13
|
+
) {}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Return 401 for authentication failure, 400 for invalid content, and 200 after callback completion or an ignored event.
|
|
17
|
+
* Operational and callback failures propagate to the route's error boundary without an acknowledgment.
|
|
18
|
+
*/
|
|
19
|
+
async handle(request: Request, onEvent: (event: Event) => Promise<void>): Promise<Response> {
|
|
20
|
+
const body = await request.text()
|
|
21
|
+
const result = await this.verifier.verify(body, request.headers)
|
|
22
|
+
if (result.outcome === 'rejected') return this.reject(result.error)
|
|
23
|
+
if (result.outcome === 'event') await onEvent(result.event)
|
|
24
|
+
return new Response(null, { status: StatusCodes.OK })
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private reject(error: WebhookVerificationError): Response {
|
|
28
|
+
this.logger.warn('Webhook rejected', { reason: error.reason })
|
|
29
|
+
const status =
|
|
30
|
+
error.reason === 'missing_headers' || error.reason === 'invalid_signature'
|
|
31
|
+
? StatusCodes.UNAUTHORIZED
|
|
32
|
+
: StatusCodes.BAD_REQUEST
|
|
33
|
+
return new Response(null, { status })
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { AppError } from '@/core'
|
|
2
|
+
|
|
3
|
+
/** A webhook cannot be trusted or parsed. The HTTP handler logs the rejection once. */
|
|
4
|
+
export class WebhookVerificationError extends AppError {
|
|
5
|
+
readonly _tag = 'WebhookVerificationError'
|
|
6
|
+
readonly classification = 'terminal'
|
|
7
|
+
|
|
8
|
+
/** Keep secrets and payload details out of the public message. */
|
|
9
|
+
constructor(
|
|
10
|
+
readonly reason: 'missing_headers' | 'invalid_signature' | 'invalid_payload',
|
|
11
|
+
cause?: unknown,
|
|
12
|
+
) {
|
|
13
|
+
super(`Webhook rejected: ${reason}`, { cause })
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** A verified event, an ignored event, or an invalid request. */
|
|
18
|
+
export type WebhookVerification<Event> =
|
|
19
|
+
| { readonly outcome: 'event'; readonly event: Event }
|
|
20
|
+
| { readonly outcome: 'ignored'; readonly type: string }
|
|
21
|
+
| { readonly outcome: 'rejected'; readonly error: WebhookVerificationError }
|
|
22
|
+
|
|
23
|
+
/** The provider owns signature verification, timestamp tolerance, and payload parsing. */
|
|
24
|
+
export interface WebhookVerifier<Event> {
|
|
25
|
+
/** Verify the exact body before parsing it. Return invalid requests; throw operational failures. */
|
|
26
|
+
verify(body: string, headers: Headers): Promise<WebhookVerification<Event>>
|
|
27
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { WebhookEventPayload } from 'resend'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
import { EmailAddressSchema, parseBounceType, type BounceType } from '@/core'
|
|
5
|
+
|
|
6
|
+
const SIMPLE_EMAIL_TYPES = [
|
|
7
|
+
'email.sent',
|
|
8
|
+
'email.scheduled',
|
|
9
|
+
'email.delivered',
|
|
10
|
+
'email.delivery_delayed',
|
|
11
|
+
'email.complained',
|
|
12
|
+
'email.opened',
|
|
13
|
+
'email.received',
|
|
14
|
+
'email.suppressed',
|
|
15
|
+
] as const
|
|
16
|
+
|
|
17
|
+
/** Resend email event names supported by this module. */
|
|
18
|
+
export type EmailEventType =
|
|
19
|
+
| (typeof SIMPLE_EMAIL_TYPES)[number]
|
|
20
|
+
| 'email.bounced'
|
|
21
|
+
| 'email.failed'
|
|
22
|
+
| 'email.clicked'
|
|
23
|
+
|
|
24
|
+
const EMAIL_TYPES = {
|
|
25
|
+
'email.sent': true,
|
|
26
|
+
'email.scheduled': true,
|
|
27
|
+
'email.delivered': true,
|
|
28
|
+
'email.delivery_delayed': true,
|
|
29
|
+
'email.complained': true,
|
|
30
|
+
'email.opened': true,
|
|
31
|
+
'email.received': true,
|
|
32
|
+
'email.suppressed': true,
|
|
33
|
+
'email.bounced': true,
|
|
34
|
+
'email.failed': true,
|
|
35
|
+
'email.clicked': true,
|
|
36
|
+
} satisfies Record<Extract<WebhookEventPayload['type'], `email.${string}`>, true>
|
|
37
|
+
|
|
38
|
+
const BaseDataSchema = z.object({
|
|
39
|
+
email_id: z.string().min(1),
|
|
40
|
+
to: z.array(EmailAddressSchema).min(1),
|
|
41
|
+
subject: z.string(),
|
|
42
|
+
tags: z.record(z.string(), z.string()).default({}),
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const EmailPayloadSchema = z.discriminatedUnion('type', [
|
|
46
|
+
z.object({ type: z.enum(SIMPLE_EMAIL_TYPES), data: BaseDataSchema }),
|
|
47
|
+
z.object({
|
|
48
|
+
type: z.literal('email.bounced'),
|
|
49
|
+
data: BaseDataSchema.extend({
|
|
50
|
+
bounce: z.object({
|
|
51
|
+
type: z.string().optional(),
|
|
52
|
+
subType: z.string().nullable().optional(),
|
|
53
|
+
message: z.string(),
|
|
54
|
+
}),
|
|
55
|
+
}),
|
|
56
|
+
}),
|
|
57
|
+
z.object({
|
|
58
|
+
type: z.literal('email.failed'),
|
|
59
|
+
data: BaseDataSchema.extend({ failed: z.object({ reason: z.string() }) }),
|
|
60
|
+
}),
|
|
61
|
+
z.object({
|
|
62
|
+
type: z.literal('email.clicked'),
|
|
63
|
+
data: BaseDataSchema.extend({ click: z.object({ link: z.string() }) }),
|
|
64
|
+
}),
|
|
65
|
+
])
|
|
66
|
+
|
|
67
|
+
type EmailEventBase = {
|
|
68
|
+
readonly id: string
|
|
69
|
+
readonly emailId: string
|
|
70
|
+
readonly recipients: readonly string[]
|
|
71
|
+
readonly subject: string
|
|
72
|
+
readonly tags: Readonly<Record<string, string>>
|
|
73
|
+
readonly occurredAt: string
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** A verified event. `id` is the Svix identity; use `resend:event:${id}` as the command id. */
|
|
77
|
+
export type EmailEvent =
|
|
78
|
+
| (EmailEventBase & {
|
|
79
|
+
readonly type: 'email.bounced'
|
|
80
|
+
readonly bounce: {
|
|
81
|
+
readonly type: BounceType
|
|
82
|
+
readonly subType: string | null
|
|
83
|
+
readonly message: string
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
| (EmailEventBase & { readonly type: 'email.failed'; readonly reason: string })
|
|
87
|
+
| (EmailEventBase & { readonly type: 'email.clicked'; readonly link: string })
|
|
88
|
+
| (EmailEventBase & { readonly type: (typeof SIMPLE_EMAIL_TYPES)[number] })
|
|
89
|
+
|
|
90
|
+
const EventHeaderSchema = z.object({
|
|
91
|
+
type: z.string(),
|
|
92
|
+
created_at: z.iso.datetime({ offset: true }),
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
/** Validate signed provider data before projecting it into the public event contract. */
|
|
96
|
+
export function parseEmailEvent(
|
|
97
|
+
payload: WebhookEventPayload,
|
|
98
|
+
id: string,
|
|
99
|
+
):
|
|
100
|
+
| { readonly outcome: 'event'; readonly event: EmailEvent }
|
|
101
|
+
| { readonly outcome: 'ignored'; readonly type: string }
|
|
102
|
+
| { readonly outcome: 'invalid' } {
|
|
103
|
+
const header = EventHeaderSchema.safeParse(payload)
|
|
104
|
+
if (!header.success) return { outcome: 'invalid' }
|
|
105
|
+
if (!Object.hasOwn(EMAIL_TYPES, header.data.type))
|
|
106
|
+
return { outcome: 'ignored', type: header.data.type }
|
|
107
|
+
const parsed = EmailPayloadSchema.safeParse(payload)
|
|
108
|
+
if (!parsed.success) return { outcome: 'invalid' }
|
|
109
|
+
const event = parsed.data
|
|
110
|
+
const base: EmailEventBase = {
|
|
111
|
+
id,
|
|
112
|
+
emailId: event.data.email_id,
|
|
113
|
+
recipients: event.data.to,
|
|
114
|
+
subject: event.data.subject,
|
|
115
|
+
tags: event.data.tags,
|
|
116
|
+
occurredAt: header.data.created_at,
|
|
117
|
+
}
|
|
118
|
+
switch (event.type) {
|
|
119
|
+
case 'email.bounced':
|
|
120
|
+
return {
|
|
121
|
+
outcome: 'event',
|
|
122
|
+
event: {
|
|
123
|
+
...base,
|
|
124
|
+
type: event.type,
|
|
125
|
+
bounce: {
|
|
126
|
+
type: parseBounceType(event.data.bounce.type),
|
|
127
|
+
subType: event.data.bounce.subType ?? null,
|
|
128
|
+
message: event.data.bounce.message,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
case 'email.failed':
|
|
133
|
+
return {
|
|
134
|
+
outcome: 'event',
|
|
135
|
+
event: { ...base, type: event.type, reason: event.data.failed.reason },
|
|
136
|
+
}
|
|
137
|
+
case 'email.clicked':
|
|
138
|
+
return { outcome: 'event', event: { ...base, type: event.type, link: event.data.click.link } }
|
|
139
|
+
default:
|
|
140
|
+
return { outcome: 'event', event: { ...base, type: event.type } }
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer'
|
|
2
|
+
|
|
3
|
+
import type { Attachment, CreateEmailOptions, ErrorResponse, Resend } from 'resend'
|
|
4
|
+
|
|
5
|
+
import type { EmailAccepted, EmailAttachment, IEmailService, OutboundEmail } from '@/application'
|
|
6
|
+
import { EmailServiceError, type EmailSendFailureReason } from '@/core'
|
|
7
|
+
|
|
8
|
+
// SDK additions must receive an explicit classification before the package builds.
|
|
9
|
+
const SDK_REASONS = {
|
|
10
|
+
invalid_idempotency_key: 'invalid_request',
|
|
11
|
+
validation_error: 'invalid_request',
|
|
12
|
+
missing_api_key: 'configuration',
|
|
13
|
+
restricted_api_key: 'configuration',
|
|
14
|
+
invalid_api_key: 'configuration',
|
|
15
|
+
not_found: 'invalid_request',
|
|
16
|
+
method_not_allowed: 'invalid_request',
|
|
17
|
+
invalid_idempotent_request: 'invalid_request',
|
|
18
|
+
concurrent_idempotent_requests: 'idempotency_conflict',
|
|
19
|
+
invalid_attachment: 'invalid_request',
|
|
20
|
+
invalid_from_address: 'invalid_request',
|
|
21
|
+
invalid_access: 'configuration',
|
|
22
|
+
invalid_parameter: 'invalid_request',
|
|
23
|
+
invalid_region: 'invalid_request',
|
|
24
|
+
missing_required_field: 'invalid_request',
|
|
25
|
+
monthly_quota_exceeded: 'quota_exceeded',
|
|
26
|
+
daily_quota_exceeded: 'quota_exceeded',
|
|
27
|
+
rate_limit_exceeded: 'rate_limited',
|
|
28
|
+
security_error: 'configuration',
|
|
29
|
+
application_error: 'provider_error',
|
|
30
|
+
internal_server_error: 'provider_error',
|
|
31
|
+
} satisfies Record<ErrorResponse['name'], EmailSendFailureReason>
|
|
32
|
+
|
|
33
|
+
// Resend documents these codes before its SDK includes them in ErrorResponse.
|
|
34
|
+
const DOCUMENTED_REASONS = new Map<string, EmailSendFailureReason>([
|
|
35
|
+
['email_above_quota', 'quota_exceeded'],
|
|
36
|
+
['invalid_permission', 'configuration'],
|
|
37
|
+
['suspended_api_key', 'configuration'],
|
|
38
|
+
['resource_locked', 'idempotency_conflict'],
|
|
39
|
+
['service_unavailable', 'provider_error'],
|
|
40
|
+
['missing_required_parameter', 'invalid_request'],
|
|
41
|
+
])
|
|
42
|
+
|
|
43
|
+
function reasonFromResend(error: ErrorResponse): EmailSendFailureReason {
|
|
44
|
+
if (Object.hasOwn(SDK_REASONS, error.name)) return SDK_REASONS[error.name]
|
|
45
|
+
const reason = DOCUMENTED_REASONS.get(error.name)
|
|
46
|
+
if (reason !== undefined) return reason
|
|
47
|
+
if (error.statusCode === 429) return 'rate_limited'
|
|
48
|
+
if (error.statusCode !== null && error.statusCode >= 400 && error.statusCode < 500)
|
|
49
|
+
return 'invalid_request'
|
|
50
|
+
return 'provider_error'
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function retryAfterSeconds(value: string | undefined): number | null {
|
|
54
|
+
if (value === undefined || !/^\d+$/.test(value)) return null
|
|
55
|
+
const seconds = Number(value)
|
|
56
|
+
return Number.isSafeInteger(seconds) ? seconds : null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function toAttachment(attachment: EmailAttachment): Attachment {
|
|
60
|
+
const result: Attachment = {
|
|
61
|
+
filename: attachment.filename,
|
|
62
|
+
content: ArrayBuffer.isView(attachment.content)
|
|
63
|
+
? Buffer.from(attachment.content)
|
|
64
|
+
: attachment.content,
|
|
65
|
+
}
|
|
66
|
+
if (attachment.contentType !== undefined) result.contentType = attachment.contentType
|
|
67
|
+
return result
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The SDK operations required by the adapter; a test factory can return a plain object. */
|
|
71
|
+
export type ResendEmailClient = { readonly emails: Pick<Resend['emails'], 'send'> }
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Send without package retries or logs. Ported from Suparelay's resend-email-service.ts.
|
|
75
|
+
* The Resend SDK itself logs API errors outside production.
|
|
76
|
+
*/
|
|
77
|
+
export class ResendEmailService implements IEmailService {
|
|
78
|
+
/** Load the SDK on the first send. Constructing the service does not load it. */
|
|
79
|
+
static create(apiKey: string, fromAddress: string): ResendEmailService {
|
|
80
|
+
return new ResendEmailService(
|
|
81
|
+
() => import('resend').then(({ Resend }) => new Resend(apiKey)),
|
|
82
|
+
fromAddress,
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private client: Promise<ResendEmailClient> | undefined
|
|
87
|
+
|
|
88
|
+
/** Supply an async factory. The service calls it once, on the first send, and keeps its promise. */
|
|
89
|
+
constructor(
|
|
90
|
+
private readonly createClient: () => Promise<ResendEmailClient>,
|
|
91
|
+
private readonly fromAddress: string,
|
|
92
|
+
) {}
|
|
93
|
+
|
|
94
|
+
/** Resolve after acceptance. Provider failures throw EmailServiceError; SDK defects retain their identity. */
|
|
95
|
+
async sendEmail(email: OutboundEmail): Promise<EmailAccepted> {
|
|
96
|
+
if (this.client === undefined) {
|
|
97
|
+
// Cache synchronous factory throws as rejections too, so later sends cannot initialize again.
|
|
98
|
+
this.client = Promise.resolve().then(this.createClient)
|
|
99
|
+
}
|
|
100
|
+
const client = await this.client
|
|
101
|
+
const payload: CreateEmailOptions = {
|
|
102
|
+
from: this.fromAddress,
|
|
103
|
+
to: [email.to].flat(),
|
|
104
|
+
subject: email.subject,
|
|
105
|
+
html: email.html,
|
|
106
|
+
}
|
|
107
|
+
if (email.text !== undefined) payload.text = email.text
|
|
108
|
+
if (email.replyTo !== undefined) payload.replyTo = email.replyTo
|
|
109
|
+
if (email.tags !== undefined)
|
|
110
|
+
payload.tags = Object.entries(email.tags).map(([name, value]) => ({ name, value }))
|
|
111
|
+
if (email.attachments !== undefined) payload.attachments = email.attachments.map(toAttachment)
|
|
112
|
+
const result = await client.emails.send(payload, { idempotencyKey: email.idempotencyKey })
|
|
113
|
+
if (result.error !== null) {
|
|
114
|
+
throw new EmailServiceError({
|
|
115
|
+
reason: reasonFromResend(result.error),
|
|
116
|
+
provider: 'resend',
|
|
117
|
+
providerCode: result.error.name,
|
|
118
|
+
statusCode: result.error.statusCode,
|
|
119
|
+
retryAfter: retryAfterSeconds(result.headers?.['retry-after']),
|
|
120
|
+
cause: result.error,
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
return { providerId: result.data.id }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Resend } from 'resend'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
WebhookVerificationError,
|
|
6
|
+
type WebhookVerification,
|
|
7
|
+
type WebhookVerifier,
|
|
8
|
+
} from '../http/webhook-verification.ts'
|
|
9
|
+
import { parseEmailEvent, type EmailEvent } from './email-event.ts'
|
|
10
|
+
|
|
11
|
+
// Separate standardwebhooks versions have different prototypes; its error name remains the same.
|
|
12
|
+
const SignatureErrorSchema = z
|
|
13
|
+
.instanceof(Error)
|
|
14
|
+
.refine((error) => error.name === 'WebhookVerificationError')
|
|
15
|
+
|
|
16
|
+
/** The SDK's local signature verifier. It makes no API request. */
|
|
17
|
+
export type ResendWebhookClient = Pick<Resend['webhooks'], 'verify'>
|
|
18
|
+
|
|
19
|
+
/** Verify Svix signatures and map Resend email events. Ported from Typist's Resend route. */
|
|
20
|
+
export class ResendWebhook implements WebhookVerifier<EmailEvent> {
|
|
21
|
+
/** Load the SDK on the first verification. Only the webhook secret is required. */
|
|
22
|
+
static create(webhookSecret: string): ResendWebhook {
|
|
23
|
+
return new ResendWebhook(webhookSecret, async () => {
|
|
24
|
+
const { Resend } = await import('resend')
|
|
25
|
+
// The SDK requires a nonempty key even though its local verifier never uses that key.
|
|
26
|
+
return new Resend('re_webhook_verification_only').webhooks
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private client: Promise<ResendWebhookClient> | undefined
|
|
31
|
+
|
|
32
|
+
/** Inject the local SDK verifier when testing. */
|
|
33
|
+
constructor(
|
|
34
|
+
private readonly webhookSecret: string,
|
|
35
|
+
private readonly verifier: () => Promise<ResendWebhookClient>,
|
|
36
|
+
) {}
|
|
37
|
+
|
|
38
|
+
/** Verify the raw body and Svix headers. Invalid requests return rejected; configuration and SDK defects propagate. */
|
|
39
|
+
async verify(body: string, headers: Headers): Promise<WebhookVerification<EmailEvent>> {
|
|
40
|
+
const id = headers.get('svix-id')
|
|
41
|
+
const timestamp = headers.get('svix-timestamp')
|
|
42
|
+
const signature = headers.get('svix-signature')
|
|
43
|
+
if (!id || !timestamp || !signature)
|
|
44
|
+
return { outcome: 'rejected', error: new WebhookVerificationError('missing_headers') }
|
|
45
|
+
const client = await (this.client ??= this.verifier())
|
|
46
|
+
let payload: ReturnType<ResendWebhookClient['verify']>
|
|
47
|
+
try {
|
|
48
|
+
payload = client.verify({
|
|
49
|
+
payload: body,
|
|
50
|
+
headers: { id, timestamp, signature },
|
|
51
|
+
webhookSecret: this.webhookSecret,
|
|
52
|
+
})
|
|
53
|
+
} catch (cause) {
|
|
54
|
+
if (cause instanceof SyntaxError)
|
|
55
|
+
return {
|
|
56
|
+
outcome: 'rejected',
|
|
57
|
+
error: new WebhookVerificationError('invalid_payload', cause),
|
|
58
|
+
}
|
|
59
|
+
if (!SignatureErrorSchema.safeParse(cause).success) throw cause
|
|
60
|
+
return {
|
|
61
|
+
outcome: 'rejected',
|
|
62
|
+
error: new WebhookVerificationError('invalid_signature', cause),
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const result = parseEmailEvent(payload, id)
|
|
66
|
+
if (result.outcome === 'invalid')
|
|
67
|
+
return { outcome: 'rejected', error: new WebhookVerificationError('invalid_payload') }
|
|
68
|
+
return result
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { createMiddleware
|
|
2
|
-
import { getRequestHeaders } from '@tanstack/react-start/server'
|
|
1
|
+
import { createMiddleware } from '@tanstack/react-start'
|
|
3
2
|
import { getSessionCookie } from 'better-auth/cookies'
|
|
4
3
|
|
|
5
4
|
import { AuthenticationError, StaleSessionError, createLogger } from '@/core'
|
|
6
5
|
|
|
6
|
+
import type { RequestContext } from './request-context.ts'
|
|
7
|
+
|
|
7
8
|
const logger = createLogger('auth-middleware')
|
|
8
9
|
|
|
9
10
|
/** The fields middleware reads; generic callers retain every additional session and user field. */
|
|
@@ -28,13 +29,19 @@ export type AuthContext<Session extends AuthSession> =
|
|
|
28
29
|
|
|
29
30
|
/** Select the current request's Better Auth instance from TanStack's registered context. */
|
|
30
31
|
export type AuthMiddlewareOptions<Session extends AuthSession> = {
|
|
31
|
-
readonly getAuth: (context:
|
|
32
|
+
readonly getAuth: (context: RequestContext) => {
|
|
32
33
|
readonly api: {
|
|
33
34
|
readonly getSession: (options: { headers: Headers }) => Promise<Session | null>
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
// The server entry loads react-dom/server; the browser evaluates this module, so it loads lazily inside `.server()`.
|
|
40
|
+
const requestHeaders = async () => {
|
|
41
|
+
const { getRequestHeaders } = await import('@tanstack/react-start/server')
|
|
42
|
+
return getRequestHeaders()
|
|
43
|
+
}
|
|
44
|
+
|
|
38
45
|
/**
|
|
39
46
|
* Preserve Porte's function and request middleware contracts with application-owned services.
|
|
40
47
|
* Optional lookup failures log once and continue as guests. Required lookup failures propagate.
|
|
@@ -46,7 +53,8 @@ export function createAuthMiddleware<Session extends AuthSession>(
|
|
|
46
53
|
async ({ next, context }) => {
|
|
47
54
|
let resolved: Session | null
|
|
48
55
|
try {
|
|
49
|
-
|
|
56
|
+
const headers = await requestHeaders()
|
|
57
|
+
resolved = await options.getAuth(context).api.getSession({ headers })
|
|
50
58
|
} catch (error) {
|
|
51
59
|
logger.error('get_session_failed', { error })
|
|
52
60
|
resolved = null
|
|
@@ -60,7 +68,7 @@ export function createAuthMiddleware<Session extends AuthSession>(
|
|
|
60
68
|
)
|
|
61
69
|
|
|
62
70
|
async function resolveRequiredSession(
|
|
63
|
-
context:
|
|
71
|
+
context: RequestContext,
|
|
64
72
|
headers: Headers,
|
|
65
73
|
): Promise<Session> {
|
|
66
74
|
const resolved = await options.getAuth(context).api.getSession({ headers })
|
|
@@ -72,7 +80,7 @@ export function createAuthMiddleware<Session extends AuthSession>(
|
|
|
72
80
|
}
|
|
73
81
|
|
|
74
82
|
const requireAuth = createMiddleware({ type: 'function' }).server(async ({ next, context }) => {
|
|
75
|
-
const resolved = await resolveRequiredSession(context,
|
|
83
|
+
const resolved = await resolveRequiredSession(context, await requestHeaders())
|
|
76
84
|
return next<RequiredAuthContext<Session>>({
|
|
77
85
|
context: { user: resolved.user, session: resolved.session },
|
|
78
86
|
})
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import { isNotFound, isRedirect,
|
|
2
|
-
import { createMiddleware
|
|
1
|
+
import { isNotFound, isRedirect, redirect } from '@tanstack/react-router'
|
|
2
|
+
import { createMiddleware } from '@tanstack/react-start'
|
|
3
3
|
import type { BetterAuthCookies } from 'better-auth'
|
|
4
4
|
|
|
5
5
|
import { StaleSessionError, type ErrorTable } from '@/core'
|
|
6
6
|
|
|
7
7
|
import { createHttpErrorResponseConverter } from './http-error-response.ts'
|
|
8
|
+
import type { RequestContext } from './request-context.ts'
|
|
8
9
|
import { clearStaleSessionCookies } from './session-cookies.ts'
|
|
9
10
|
import { createErrorPayloadConverter } from './to-error-payload.ts'
|
|
10
11
|
|
|
11
|
-
type RequestContext = RequestServerOptions<Register, undefined>['context']
|
|
12
|
-
|
|
13
12
|
/** The part of a Better Auth instance the boundary reads: its computed cookie definitions. */
|
|
14
13
|
export type AuthCookieSource = {
|
|
15
14
|
readonly $context: Promise<{ readonly authCookies: BetterAuthCookies }>
|
|
@@ -18,8 +17,8 @@ export type AuthCookieSource = {
|
|
|
18
17
|
/** The application's error table, its stale-session redirect, and its Better Auth instance. */
|
|
19
18
|
export type ErrorMiddlewareOptions<Entries> = {
|
|
20
19
|
readonly errors: ErrorTable<Entries>
|
|
21
|
-
/**
|
|
22
|
-
readonly signIn:
|
|
20
|
+
/** The path a server function redirects to for a stale session, with any search string. */
|
|
21
|
+
readonly signIn: string
|
|
23
22
|
/** Select the request's Better Auth instance; its cookie definitions say what to clear. */
|
|
24
23
|
readonly getAuth: (context: RequestContext) => AuthCookieSource
|
|
25
24
|
}
|
|
@@ -34,7 +33,7 @@ export function createErrorMiddleware<Entries>(options: ErrorMiddlewareOptions<E
|
|
|
34
33
|
|
|
35
34
|
async function clearSession(context: RequestContext): Promise<void> {
|
|
36
35
|
const { authCookies } = await options.getAuth(context).$context
|
|
37
|
-
clearStaleSessionCookies(authCookies)
|
|
36
|
+
await clearStaleSessionCookies(authCookies)
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
async function handleFunctionError(cause: unknown, context: RequestContext): Promise<never> {
|
|
@@ -43,7 +42,7 @@ export function createErrorMiddleware<Entries>(options: ErrorMiddlewareOptions<E
|
|
|
43
42
|
if (cause instanceof StaleSessionError) {
|
|
44
43
|
await clearSession(context)
|
|
45
44
|
// oxlint-disable-next-line typescript/only-throw-error -- Router redirects by throwing this.
|
|
46
|
-
throw options.signIn
|
|
45
|
+
throw redirect({ href: options.signIn })
|
|
47
46
|
}
|
|
48
47
|
// oxlint-disable-next-line typescript/only-throw-error -- The client rejects with this and reads its tag.
|
|
49
48
|
throw toErrorPayload(cause)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Register } from '@tanstack/react-start'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The request context an application registers as `Register.server.requestContext`.
|
|
5
|
+
* TanStack's own request context type also reads `Register.config`, which lists the middleware
|
|
6
|
+
* whose options carry this type, so using it here would make every application's types circular.
|
|
7
|
+
* Without a registration TanStack passes no context, so the fallback is `undefined`.
|
|
8
|
+
*/
|
|
9
|
+
export type RequestContext = Register extends { server: { requestContext: infer Context } }
|
|
10
|
+
? Context
|
|
11
|
+
: undefined
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-only helpers for TanStack Start. This entry evaluates `@tanstack/react-start/server`,
|
|
3
|
+
* so only code the Start compiler keeps on the server may import it: a `.server()` callback,
|
|
4
|
+
* a route loader's server function, or a request handler. `better-ship/tanstack` stays safe for
|
|
5
|
+
* a module the browser evaluates.
|
|
6
|
+
*/
|
|
7
|
+
export { getLastUsedLoginMethod } from './last-login-method.ts'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { deleteCookie
|
|
1
|
+
import type { deleteCookie } from '@tanstack/react-start/server'
|
|
2
2
|
import type { BetterAuthCookies } from 'better-auth'
|
|
3
3
|
import { parseCookies } from 'better-auth/cookies'
|
|
4
4
|
|
|
@@ -9,8 +9,10 @@ type DeleteCookieOptions = NonNullable<Parameters<typeof deleteCookie>[1]>
|
|
|
9
9
|
* Delete the session, session cache, account cache, and remember-me cookies of one instance.
|
|
10
10
|
* Chunks present on the request go too. Names and attributes come from the instance, so custom prefixes and domain cookies clear.
|
|
11
11
|
*/
|
|
12
|
-
export function clearStaleSessionCookies(cookies: BetterAuthCookies): void {
|
|
13
|
-
|
|
12
|
+
export async function clearStaleSessionCookies(cookies: BetterAuthCookies): Promise<void> {
|
|
13
|
+
// The server entry loads react-dom/server; the browser evaluates this module, so it loads lazily here.
|
|
14
|
+
const server = await import('@tanstack/react-start/server')
|
|
15
|
+
const present = [...parseCookies(server.getRequestHeaders().get('cookie') ?? '').keys()]
|
|
14
16
|
const definitions = [
|
|
15
17
|
cookies.sessionToken,
|
|
16
18
|
cookies.sessionData,
|
|
@@ -21,7 +23,7 @@ export function clearStaleSessionCookies(cookies: BetterAuthCookies): void {
|
|
|
21
23
|
// Better Auth names a chunk `${name}.${index}`, so a missed chunk would resurrect the value.
|
|
22
24
|
const chunks = present.filter((name) => name.startsWith(`${cookie.name}.`))
|
|
23
25
|
for (const name of new Set([cookie.name, ...chunks]))
|
|
24
|
-
deleteCookie(name, deletionOf(cookie.attributes))
|
|
26
|
+
server.deleteCookie(name, deletionOf(cookie.attributes))
|
|
25
27
|
}
|
|
26
28
|
}
|
|
27
29
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"application-DYfcAy-Q.js","names":[],"sources":["../src/application/message-already-processed.error.ts","../src/application/message-bus.ts","../src/application/unit-of-work.ts","../src/application/unit-of-work.errors.ts"],"sourcesContent":["import { AppError, DatabaseError, type MessageId } from '@/core'\n\n/** The message's effects already committed. A caller can skip duplicate delivery without repeating those effects. */\nexport class MessageAlreadyProcessedError extends AppError {\n readonly _tag = 'MessageAlreadyProcessedError'\n readonly classification = 'terminal'\n\n constructor(\n readonly messageId: MessageId,\n options?: { readonly cause: unknown },\n ) {\n super(`message ${messageId} already processed`, options)\n }\n\n /** Recognize only the processed-message primary key; preserve other failures unchanged at the call site. */\n static from(cause: unknown, messageId: MessageId): MessageAlreadyProcessedError | undefined {\n if (cause instanceof MessageAlreadyProcessedError) return cause\n if (!(cause instanceof DatabaseError)) return undefined\n const details = cause.details\n const duplicate =\n details.database === 'd1'\n ? details.kind === 'unique' &&\n details.table === 'processed_messages' &&\n details.column === 'message_id'\n : details.code === '23505' &&\n details.table_name === 'processed_messages' &&\n details.constraint_name === 'processed_messages_pkey'\n if (!duplicate) return undefined\n return new MessageAlreadyProcessedError(messageId, { cause })\n }\n}\n","import { panic } from 'better-result'\n\nimport {\n createLogger,\n unreachable,\n type DomainCommand,\n type DomainEvent,\n type DomainMessage,\n type DomainQuery,\n} from '@/core'\n\nimport { MessageAlreadyProcessedError } from './message-already-processed.error.ts'\nimport type { Handlers, MessageResult, RegistryMessage } from './registry.ts'\n\nconst logger = createLogger('message-bus')\n\n/** The port an entrypoint dispatches through. `AppDeps` names this as `IMessageBus<typeof registry>`. */\nexport interface IMessageBus<TRegistry extends Handlers<never>> {\n handle<TMessage extends DomainMessage & RegistryMessage<TRegistry>>(\n message: TMessage,\n ): Promise<MessageResult<TMessage, TRegistry>>\n}\n\n/**\n * Routes one message to its slot. The bus opens no transaction and keeps no outbox:\n * a handler that writes opens the unit of work itself.\n */\nexport class MessageBus<Deps, TRegistry extends Handlers<Deps>> implements IMessageBus<TRegistry> {\n constructor(\n private readonly deps: Deps,\n private readonly registry: TRegistry,\n ) {}\n\n /** Resolves to the handler's own value for a command or query, and to void for an event. */\n async handle<TMessage extends DomainMessage & RegistryMessage<TRegistry>>(\n message: TMessage,\n ): Promise<MessageResult<TMessage, TRegistry>> {\n // SAFETY: the slot for this name is what `MessageResult` names, and dispatch returns its value.\n return (await this.dispatch(message)) as MessageResult<TMessage, TRegistry>\n }\n\n // oxlint-disable-next-line anti-slop/no-unknown-returns -- one table holds every slot's value; `handle` names it.\n private dispatch(message: DomainMessage): Promise<unknown> {\n if (message.type === 'command') return this.handleCommand(message)\n if (message.type === 'query') return this.handleQuery(message)\n if (message.type === 'event') return this.handleEvent(message)\n // A message parsed at a boundary cannot reach here; one that does must not resolve silently.\n return unreachable(message)\n }\n\n // oxlint-disable-next-line anti-slop/no-unknown-returns -- see dispatch.\n private handleCommand(command: DomainCommand): Promise<unknown> {\n // `satisfies` proved the registry complete, so a missing slot is a broken registry object.\n const handler = this.registry.commands[command.name]\n if (handler === undefined) return panic(`No handler for command ${command.name}`)\n logger.debug('handling_command', { name: command.name, id: command.id })\n return handler(command, this.deps)\n }\n\n // oxlint-disable-next-line anti-slop/no-unknown-returns -- see dispatch.\n private handleQuery(query: DomainQuery): Promise<unknown> {\n const handler = this.registry.queries[query.name]\n if (handler === undefined) return panic(`No handler for query ${query.name}`)\n return handler(query, this.deps)\n }\n\n /**\n * Every subscriber runs to completion. One failure is rethrown as is. Several are thrown as\n * one `AggregateError`. A `MessageAlreadyProcessedError` means that subscriber already committed on\n * an earlier delivery, so it counts as delivered.\n */\n private async handleEvent(event: DomainEvent): Promise<void> {\n const subscriptions = this.registry.events[event.name] ?? []\n const outcomes = await Promise.allSettled(\n subscriptions.map((subscription) => subscription(event, this.deps)),\n )\n const failures: unknown[] = []\n for (const [subscriber, outcome] of outcomes.entries()) {\n if (outcome.status === 'fulfilled') continue\n if (outcome.reason instanceof MessageAlreadyProcessedError) {\n // The bus swallows this error, so this line is the only record that the skip happened.\n logger.info('duplicate_subscriber_ignored', { event: event.name, id: event.id, subscriber })\n continue\n }\n failures.push(outcome.reason)\n }\n if (failures.length === 1) throw failures[0]\n if (failures.length > 1) {\n throw new AggregateError(failures, `${event.name}: ${failures.length} subscribers failed`)\n }\n }\n}\n","import type { MessageId } from '@/core'\n\nimport type { IEventCollector } from './event-collector.ts'\nimport { MessageAlreadyProcessedError } from './message-already-processed.error.ts'\nimport type { IMessageStore } from './message-store.ts'\nimport type { IOutboxRelay } from './outbox-relay.ts'\nimport type { ITransaction } from './transaction.ts'\n\n/**\n * Commits the processed-message record, repository writes, and emitted events together before resolving.\n * An existing record rejects with `MessageAlreadyProcessedError`; external effects must run through the outbox after commit.\n * Nested calls reject with `UnitOfWorkAlreadyActiveError`; independent concurrent calls are allowed.\n */\nexport interface IUnitOfWork<Repos> {\n run<T>(messageId: MessageId, work: (repos: Repos) => Promise<T>): Promise<T>\n}\n\nexport type UnitOfWorkDeps<Tx, Repos> = {\n readonly transaction: ITransaction<Tx>\n /** The processed-messages and outbox repository for this transaction. */\n readonly messageStore: (tx: Tx) => IMessageStore\n /** Repositories participate in the current transaction. */\n readonly createRepositories: (tx: Tx) => Repos\n readonly eventCollector: IEventCollector\n readonly relay: IOutboxRelay\n}\n\n/** The unit of work: the boundary, the repositories, the events. Every database call is a port. */\nexport class UnitOfWork<Tx, Repos> implements IUnitOfWork<Repos> {\n constructor(private readonly deps: UnitOfWorkDeps<Tx, Repos>) {}\n\n async run<T>(messageId: MessageId, work: (repos: Repos) => Promise<T>): Promise<T> {\n const { transaction, messageStore, createRepositories, eventCollector, relay } = this.deps\n let written = 0\n\n let value: T\n try {\n value = await transaction.transaction(async (tx) => {\n const store = messageStore(tx)\n await store.claim(messageId)\n const repos = createRepositories(tx)\n const result = await work(repos)\n const pending = eventCollector.collect()\n await store.persistEvents(pending)\n written = pending.length\n return result\n })\n } catch (error) {\n throw MessageAlreadyProcessedError.from(error, messageId) ?? error\n }\n\n if (written > 0) relay.wake()\n return value\n }\n}\n","import { AppError } from '@/core'\n\n/** A boundary was opened inside another. One message, one atomic scope. */\nexport class UnitOfWorkAlreadyActiveError extends AppError {\n readonly _tag = 'UnitOfWorkAlreadyActiveError'\n readonly classification = 'terminal'\n\n constructor() {\n super('a unit of work is already active')\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index-6XMPJu7u.d.ts","names":[],"sources":["../src/core/failure-classification.ts","../src/core/app-error.ts","../src/core/authentication.error.ts","../src/core/rate-limit.error.ts","../src/core/stale-session.error.ts","../src/core/not-authorized.error.ts","../src/core/internal.errors.ts","../src/core/problem-details.ts","../src/core/request.errors.ts","../src/core/error-table.ts","../src/core/database.error.ts","../src/core/defects.ts","../src/core/identity.ts","../src/core/messages.ts","../src/core/define-messages.ts","../src/core/entity.ts","../src/core/transport-classification.ts","../src/core/cloudflare-classification.ts","../src/core/classify-failure.ts","../src/core/backoff.ts","../src/core/logger.ts"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index-_Cihfq1J.d.ts","names":[],"sources":["../src/application/handlers.ts","../src/application/message-store.ts","../src/application/registry.ts","../src/application/message-bus.ts","../src/application/outbox-relay.ts","../src/application/transaction.ts","../src/application/event-collector.ts","../src/application/unit-of-work.ts","../src/application/message-already-processed.error.ts","../src/application/unit-of-work.errors.ts"],"mappings":";;;;;;KAMY,eAAe,iBAAiB,eAAe,MAAM,UAC/D,SAAS,UACT,MAAM,SACH,QAAQ;;;;;KAMD,aAAa,eAAe,aAAa,WAAW,UAC9D,OAAO,QACP,MAAM,cACH,QAAQ;;KAGD,aAAa,eAAe,aAAa,SACnD,OAAO,QACP,MAAM,SACH;;;;;;;UClBY;;EAEf,MAAM,WAAW,YAAY;;EAE7B,cAAc,QAAQ,cAAc,eAAe;;;;;KCLzC,gBAAgB,iBAAiB,eAAe,oBAChD,KAAK,YAAY,YAAY,eAAe,GAAG;;KAI/C,cAAc,eAAe,aAAa,yBAC1C,KAAK,UAAU,YAAY,aAAa,GAAG;;;;;KAO3C,cAAc,eAAe,aAAa,oBAC1C,KAAK,UAAU,aAAa,cAAc,aAAa,GAAG;;;;;;KAQ1D,gBAAgB,iBAAiB,eAAe,aAAa,WAAW;WACzE,UAAU,gBAAgB,QAAQ,UAAU,gBAAgB;WAC5D,SAAS,cAAc,QAAQ,UAAU,cAAc;WACvD,QAAQ,cAAc,QAAQ,UAAU,cAAc;;KAK5D,KAAK;EAAU,OAAO,SAAS,eAAe,MAAM,OAAO;;KAC3D,aAAa;EAAU,OAAO,OAAO,aAAa,MAAM,OAAO;;;;;;KAMxD,SAAS;WACV,UAAU,SAAS,eAAe,KAAK;WACvC,SAAS,SAAS,eAAe,KAAK;WACtC,QAAQ,SAAS,QAAQ,eAAe,cAAc,aAAa;;KAGzE,YAAY,UAAU,WAAW,QAAQ,aAAa,SAAS;KAE/D,oBAAoB,UACvB,YAAY,aAAa,iBAAiB,oBAAoB,iBAC1D,WAAW,QAAQ,eAAe;;KAI5B,gBAAgB,kBAAkB,mBAC1C,YAAY,yBACZ,YAAY,wBACZ,oBAAoB;KAEnB,UAAU,QAAQ,wBAAwB,oBAAoB,SAC/D,QAAQ,WAAW,QAAQ,OAAO,QAAQ;;KAIlC,cACV,iBAAiB,eACjB,kBAAkB,mBAChB,iBAAiB,gBACjB,UAAU,uBAAuB,oBACjC,iBAAiB,cACf,UAAU,sBAAsB;;;;UCvDrB,YAAY,kBAAkB;EAC7C,OAAO,iBAAiB,gBAAgB,gBAAgB,YACtD,SAAS,WACR,QAAQ,cAAc,UAAU;;;;;;cAOxB,WAAW,MAAM,kBAAkB,SAAS,kBAAkB,YAAY;mBAElE;mBACA;EAFnB,YACmB,MAAM,MACN,UAAU;;EAIvB,OAAO,iBAAiB,gBAAgB,gBAAgB,YAC5D,SAAS,WACR,QAAQ,cAAc,UAAU;UAM3B;UASA;UASA;;;;;;UAWM;;;;;;;;;UClEC;EACf;;;;;;;;;UCDe,aAAa;EAC5B,YAAY,GAAG,OAAO,IAAI,OAAO,QAAQ,KAAK,QAAQ;;;;;UCHvC;EACf,KAAK,OAAO;EACZ,WAAW,cAAc;;;;;;;;;UCQV,YAAY;EAC3B,IAAI,GAAG,WAAW,WAAW,OAAO,OAAO,UAAU,QAAQ,KAAK,QAAQ;;KAGhE,eAAe,IAAI;WACpB,aAAa,aAAa;;WAE1B,eAAe,IAAI,OAAO;;WAE1B,qBAAqB,IAAI,OAAO;WAChC,gBAAgB;WAChB,OAAO;;;cAIL,WAAW,IAAI,kBAAkB,YAAY;mBAC3B;EAA7B,YAA6B,MAAM,eAAe,IAAI;EAEhD,IAAI,GAAG,WAAW,WAAW,OAAO,OAAO,UAAU,QAAQ,KAAK,QAAQ;;;;;cC5BrE,qCAAqC;WAKrC,WAAW;WAJb;WACA;EAET,YACW,WAAW,WACpB;aAAqB;;;SAMhB,KAAK,gBAAgB,WAAW,YAAY;;;;;cCZxC,qCAAqC;WACvC;WACA;EAET"}
|