@agent-native/core 0.140.0 → 0.141.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/corpus/README.md +1 -1
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +1 -1
- package/corpus/templates/analytics/changelog/2026-08-05-historical-analytics-rollups-stay-complete-and-atomic.md +6 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +0 -2
- package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +11 -6
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +8 -18
- package/corpus/templates/analytics/server/plugins/db.ts +174 -0
- package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +38 -38
- package/corpus/templates/calendar/changelog/2026-08-05-out-of-office-blocks-now-open-from-their-scheduled-time-rang.md +6 -0
- package/corpus/templates/calendar/server/lib/booking-emails.ts +70 -154
- package/corpus/templates/calendar/server/lib/event-guest-notifications.ts +28 -64
- package/corpus/templates/clips/actions/get-recording-player-data.ts +1 -0
- package/corpus/templates/clips/actions/invite-member.ts +16 -39
- package/corpus/templates/clips/app/components/player/comments-panel.tsx +2 -3
- package/corpus/templates/clips/app/components/player/reaction-emojis.ts +10 -0
- package/corpus/templates/clips/app/components/player/reactions-tray.tsx +2 -2
- package/corpus/templates/clips/app/components/player/video-player.tsx +20 -6
- package/corpus/templates/clips/app/routes/embed.$shareId.tsx +33 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +5 -1
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +27 -1
- package/corpus/templates/clips/changelog/2026-08-05-comment-and-video-reaction-pickers-now-include-eyes-and-mind.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-05-fresh-recordings-now-open-with-the-complete-video.md +6 -0
- package/corpus/templates/clips/server/lib/transactional-email-templates.ts +0 -24
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +1 -0
- package/corpus/templates/content/server/lib/comment-notifications.ts +26 -57
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +1 -44
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +1 -265
- package/corpus/templates/design/actions/create-design-system.ts +10 -9
- package/corpus/templates/design/actions/get-design-system.ts +0 -53
- package/corpus/templates/design/actions/import-design-tokens.ts +71 -5
- package/corpus/templates/design/actions/index-design-tokens.ts +80 -61
- package/corpus/templates/design/actions/update-design-system.ts +10 -7
- package/corpus/templates/design/app/components/design/EditPanel.tsx +2 -45
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +0 -12
- package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +6 -10
- package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +3 -19
- package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +10 -36
- package/corpus/templates/design/app/components/design/edit-panel/interaction-state-helpers.ts +0 -15
- package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +1 -19
- package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +0 -6
- package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +23 -53
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +4 -142
- package/corpus/templates/design/app/components/design/types.ts +0 -6
- package/corpus/templates/design/app/i18n-data.ts +0 -20
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +1 -1
- package/corpus/templates/design/app/pages/DesignSystems.tsx +1 -78
- package/corpus/templates/design/app/pages/design-editor/html-layer-positioning.ts +1 -6
- package/corpus/templates/design/e2e/helpers.ts +0 -17
- package/corpus/templates/design/shared/resolve-tweaks.ts +18 -10
- package/corpus/templates/forms/server/lib/response-email.ts +0 -2
- package/corpus/templates/plan/actions/request-plan-access.ts +12 -33
- package/corpus/templates/plan/server/lib/comment-notifications.ts +27 -53
- package/corpus/templates/slides/server/lib/comment-notifications.ts +21 -44
- package/dist/a2a/handlers.js +9 -10
- package/dist/brand-kit/index.d.ts +0 -1
- package/dist/brand-kit/index.js +0 -1
- package/dist/brand-kit/types.d.ts +0 -25
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/db/client.js +8 -8
- package/dist/db/migrations.js +39 -19
- package/dist/localization/default-messages.d.ts +0 -45
- package/dist/localization/default-messages.js +0 -45
- package/dist/notifications/routes.d.ts +6 -6
- package/dist/observability/routes.d.ts +3 -3
- package/dist/org/handlers.js +1 -8
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
- package/dist/provider-api/actions/provider-api.d.ts +7 -7
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-discovery.js +0 -22
- package/dist/server/auth.js +3 -1
- package/dist/server/better-auth-instance.js +2 -17
- package/dist/server/email.d.ts +0 -9
- package/dist/server/email.js +3 -44
- package/dist/server/index.d.ts +0 -1
- package/dist/server/index.js +0 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +3 -2
- package/docs/content/authentication.mdx +25 -19
- package/package.json +1 -1
- package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +3 -2
- package/corpus/templates/analytics/server/lib/emails.ts +0 -51
- package/corpus/templates/analytics/server/plugins/transactional-emails.ts +0 -5
- package/corpus/templates/calendar/server/lib/emails.ts +0 -167
- package/corpus/templates/calendar/server/plugins/transactional-emails.ts +0 -5
- package/corpus/templates/clips/server/lib/emails.ts +0 -256
- package/corpus/templates/clips/server/plugins/transactional-email-catalog.ts +0 -5
- package/corpus/templates/content/server/lib/emails.ts +0 -70
- package/corpus/templates/content/server/plugins/transactional-emails.ts +0 -5
- package/corpus/templates/design/app/components/design/edit-panel/design-system-swatches.ts +0 -215
- package/corpus/templates/design/changelog/2026-08-04-design-system-tokens-show-by-name-in-the-editor.md +0 -6
- package/corpus/templates/design/server/lib/design-system-tokens.ts +0 -35
- package/corpus/templates/design/shared/authored-color-styles.ts +0 -401
- package/corpus/templates/dispatch/app/routes/transactional-email.$appId.$id.tsx +0 -4
- package/corpus/templates/dispatch/app/routes/transactional-email.tsx +0 -4
- package/corpus/templates/forms/server/lib/emails.ts +0 -52
- package/corpus/templates/forms/server/plugins/transactional-emails.ts +0 -5
- package/corpus/templates/plan/server/lib/emails.ts +0 -67
- package/corpus/templates/plan/server/plugins/transactional-emails.ts +0 -5
- package/corpus/templates/slides/server/lib/emails.ts +0 -40
- package/corpus/templates/slides/server/plugins/transactional-emails.ts +0 -5
- package/dist/brand-kit/tokens.d.ts +0 -92
- package/dist/brand-kit/tokens.js +0 -280
- package/dist/email-catalog/actions/list-email-activity.d.ts +0 -6
- package/dist/email-catalog/actions/list-email-activity.js +0 -15
- package/dist/email-catalog/actions/list-email-engagement.d.ts +0 -6
- package/dist/email-catalog/actions/list-email-engagement.js +0 -15
- package/dist/email-catalog/actions/list-email-log.d.ts +0 -8
- package/dist/email-catalog/actions/list-email-log.js +0 -22
- package/dist/email-catalog/actions/list-transactional-emails.d.ts +0 -23
- package/dist/email-catalog/actions/list-transactional-emails.js +0 -61
- package/dist/email-catalog/actions/render-transactional-email-preview.d.ts +0 -10
- package/dist/email-catalog/actions/render-transactional-email-preview.js +0 -24
- package/dist/email-catalog/authorize.d.ts +0 -2
- package/dist/email-catalog/authorize.js +0 -10
- package/dist/email-catalog/log.d.ts +0 -57
- package/dist/email-catalog/log.js +0 -117
- package/dist/email-catalog/provider-metrics.d.ts +0 -34
- package/dist/email-catalog/provider-metrics.js +0 -165
- package/dist/email-catalog/registry.d.ts +0 -79
- package/dist/email-catalog/registry.js +0 -71
- package/dist/email-catalog/schema.d.ts +0 -212
- package/dist/email-catalog/schema.js +0 -49
- package/dist/email-catalog/system-emails.d.ts +0 -14
- package/dist/email-catalog/system-emails.js +0 -68
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Engagement metrics and activity for registered transactional emails, read
|
|
3
|
-
* from the active provider in the current app's request context.
|
|
4
|
-
*/
|
|
5
|
-
export type ProviderMetricsResult<T> = {
|
|
6
|
-
available: true;
|
|
7
|
-
data: T;
|
|
8
|
-
} | {
|
|
9
|
-
available: false;
|
|
10
|
-
reason: string;
|
|
11
|
-
};
|
|
12
|
-
export interface EmailEngagement {
|
|
13
|
-
templateId: string;
|
|
14
|
-
delivered: number;
|
|
15
|
-
uniqueOpens: number;
|
|
16
|
-
uniqueClicks: number;
|
|
17
|
-
openRate: number | null;
|
|
18
|
-
}
|
|
19
|
-
export declare function fetchEmailEngagement(templateIds: string[], windowDays: number): Promise<ProviderMetricsResult<EmailEngagement[]>>;
|
|
20
|
-
export interface EmailActivityEntry {
|
|
21
|
-
msgId: string;
|
|
22
|
-
toEmail: string;
|
|
23
|
-
fromEmail: string;
|
|
24
|
-
subject: string;
|
|
25
|
-
status: string;
|
|
26
|
-
opensCount: number;
|
|
27
|
-
clicksCount: number;
|
|
28
|
-
lastEventTime: string;
|
|
29
|
-
}
|
|
30
|
-
export declare function fetchEmailActivity(options: {
|
|
31
|
-
templateId: string;
|
|
32
|
-
limit?: number;
|
|
33
|
-
}): Promise<ProviderMetricsResult<EmailActivityEntry[]>>;
|
|
34
|
-
//# sourceMappingURL=provider-metrics.d.ts.map
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Engagement metrics and activity for registered transactional emails, read
|
|
3
|
-
* from the active provider in the current app's request context.
|
|
4
|
-
*/
|
|
5
|
-
import { z } from "zod";
|
|
6
|
-
import { resolveSecret } from "../server/credential-provider.js";
|
|
7
|
-
import { getEmailProvider } from "../server/email.js";
|
|
8
|
-
const SENDGRID_API = "https://api.sendgrid.com/v3";
|
|
9
|
-
const SENDGRID_PAGE_SIZE = 1000;
|
|
10
|
-
const SENDGRID_MAX_PAGES = 10;
|
|
11
|
-
const categoryMetricsSchema = z
|
|
12
|
-
.object({
|
|
13
|
-
delivered: z.number().default(0),
|
|
14
|
-
unique_opens: z.number().default(0),
|
|
15
|
-
unique_clicks: z.number().default(0),
|
|
16
|
-
})
|
|
17
|
-
.passthrough();
|
|
18
|
-
const categoryStatSchema = z
|
|
19
|
-
.object({
|
|
20
|
-
name: z.string(),
|
|
21
|
-
metrics: categoryMetricsSchema.default(() => ({
|
|
22
|
-
delivered: 0,
|
|
23
|
-
unique_opens: 0,
|
|
24
|
-
unique_clicks: 0,
|
|
25
|
-
})),
|
|
26
|
-
})
|
|
27
|
-
.passthrough();
|
|
28
|
-
const categorySumsSchema = z.object({
|
|
29
|
-
stats: z.array(categoryStatSchema).default([]),
|
|
30
|
-
});
|
|
31
|
-
const activitySchema = z.object({
|
|
32
|
-
messages: z
|
|
33
|
-
.array(z
|
|
34
|
-
.object({
|
|
35
|
-
msg_id: z.string().default(""),
|
|
36
|
-
to_email: z.string().default(""),
|
|
37
|
-
from_email: z.string().default(""),
|
|
38
|
-
subject: z.string().default(""),
|
|
39
|
-
status: z.string().default(""),
|
|
40
|
-
opens_count: z.coerce.number().default(0),
|
|
41
|
-
clicks_count: z.coerce.number().default(0),
|
|
42
|
-
last_event_time: z.string().default(""),
|
|
43
|
-
})
|
|
44
|
-
.passthrough())
|
|
45
|
-
.default([]),
|
|
46
|
-
});
|
|
47
|
-
async function activeSendGrid() {
|
|
48
|
-
const provider = await getEmailProvider();
|
|
49
|
-
if (provider === "resend") {
|
|
50
|
-
return {
|
|
51
|
-
available: false,
|
|
52
|
-
reason: "Email delivery uses Resend, so SendGrid metrics do not describe the active transport.",
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
if (provider !== "sendgrid") {
|
|
56
|
-
return {
|
|
57
|
-
available: false,
|
|
58
|
-
reason: "No email provider is configured, so provider metrics cannot be read.",
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
const key = await resolveSecret("SENDGRID_API_KEY");
|
|
62
|
-
if (!key) {
|
|
63
|
-
return {
|
|
64
|
-
available: false,
|
|
65
|
-
reason: "SendGrid is the active email transport, but SENDGRID_API_KEY could not be resolved.",
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
return { available: true, key };
|
|
69
|
-
}
|
|
70
|
-
function isoDate(ms) {
|
|
71
|
-
return new Date(ms).toISOString().slice(0, 10);
|
|
72
|
-
}
|
|
73
|
-
async function sendgridGet(key, path, params) {
|
|
74
|
-
const url = new URL(`${SENDGRID_API}${path}`);
|
|
75
|
-
for (const [name, value] of params)
|
|
76
|
-
url.searchParams.append(name, value);
|
|
77
|
-
const res = await fetch(url, {
|
|
78
|
-
headers: { Authorization: `Bearer ${key}` },
|
|
79
|
-
});
|
|
80
|
-
if (!res.ok) {
|
|
81
|
-
const body = await res
|
|
82
|
-
.text()
|
|
83
|
-
.catch((cause) => `<error body unreadable: ${cause}>`);
|
|
84
|
-
throw new Error(`SendGrid ${res.status} on ${path}: ${body.slice(0, 300)}`);
|
|
85
|
-
}
|
|
86
|
-
return res.json();
|
|
87
|
-
}
|
|
88
|
-
export async function fetchEmailEngagement(templateIds, windowDays) {
|
|
89
|
-
if (!templateIds.length)
|
|
90
|
-
return { available: true, data: [] };
|
|
91
|
-
const sendgrid = await activeSendGrid();
|
|
92
|
-
if (!sendgrid.available)
|
|
93
|
-
return sendgrid;
|
|
94
|
-
const end = Date.now();
|
|
95
|
-
const start = end - windowDays * 24 * 60 * 60 * 1000;
|
|
96
|
-
const requested = new Set(templateIds);
|
|
97
|
-
const data = [];
|
|
98
|
-
try {
|
|
99
|
-
for (let page = 0; page < SENDGRID_MAX_PAGES; page += 1) {
|
|
100
|
-
const payload = categorySumsSchema.parse(await sendgridGet(sendgrid.key, "/categories/stats/sums", [
|
|
101
|
-
["start_date", isoDate(start)],
|
|
102
|
-
["end_date", isoDate(end)],
|
|
103
|
-
["limit", String(SENDGRID_PAGE_SIZE)],
|
|
104
|
-
["offset", String(page * SENDGRID_PAGE_SIZE)],
|
|
105
|
-
]));
|
|
106
|
-
for (const entry of payload.stats) {
|
|
107
|
-
if (!requested.has(entry.name))
|
|
108
|
-
continue;
|
|
109
|
-
requested.delete(entry.name);
|
|
110
|
-
const delivered = entry.metrics.delivered;
|
|
111
|
-
const uniqueOpens = entry.metrics.unique_opens;
|
|
112
|
-
data.push({
|
|
113
|
-
templateId: entry.name,
|
|
114
|
-
delivered,
|
|
115
|
-
uniqueOpens,
|
|
116
|
-
uniqueClicks: entry.metrics.unique_clicks,
|
|
117
|
-
openRate: delivered > 0 ? Math.min(uniqueOpens / delivered, 1) : null,
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
if (requested.size === 0 || payload.stats.length < SENDGRID_PAGE_SIZE) {
|
|
121
|
-
break;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
return { available: true, data };
|
|
125
|
-
}
|
|
126
|
-
catch (error) {
|
|
127
|
-
return {
|
|
128
|
-
available: false,
|
|
129
|
-
reason: error instanceof Error ? error.message : String(error),
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
export async function fetchEmailActivity(options) {
|
|
134
|
-
const sendgrid = await activeSendGrid();
|
|
135
|
-
if (!sendgrid.available)
|
|
136
|
-
return sendgrid;
|
|
137
|
-
const limit = Math.min(Math.max(options.limit ?? 50, 1), 1000);
|
|
138
|
-
const safe = options.templateId.replace(/["\\]/g, "");
|
|
139
|
-
try {
|
|
140
|
-
const payload = activitySchema.parse(await sendgridGet(sendgrid.key, "/messages", [
|
|
141
|
-
["limit", String(limit)],
|
|
142
|
-
["query", `category="${safe}"`],
|
|
143
|
-
]));
|
|
144
|
-
return {
|
|
145
|
-
available: true,
|
|
146
|
-
data: payload.messages.map((message) => ({
|
|
147
|
-
msgId: message.msg_id,
|
|
148
|
-
toEmail: message.to_email,
|
|
149
|
-
fromEmail: message.from_email,
|
|
150
|
-
subject: message.subject,
|
|
151
|
-
status: message.status,
|
|
152
|
-
opensCount: message.opens_count,
|
|
153
|
-
clicksCount: message.clicks_count,
|
|
154
|
-
lastEventTime: message.last_event_time,
|
|
155
|
-
})),
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
catch (error) {
|
|
159
|
-
return {
|
|
160
|
-
available: false,
|
|
161
|
-
reason: error instanceof Error ? error.message : String(error),
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
//# sourceMappingURL=provider-metrics.js.map
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Registry of the transactional emails an app can send.
|
|
3
|
-
*
|
|
4
|
-
* An app declares each email it sends here so the workspace can answer, without
|
|
5
|
-
* reading the app's source: what emails exist, what makes one send, who it goes
|
|
6
|
-
* to, who it comes from, and what it looks like. Dispatch aggregates these
|
|
7
|
-
* across every mounted app via the `list-transactional-emails` action.
|
|
8
|
-
*
|
|
9
|
-
* Declare emails next to the code that sends them, then import that module from
|
|
10
|
-
* a server plugin so registration happens at startup:
|
|
11
|
-
*
|
|
12
|
-
* defineTransactionalEmail({
|
|
13
|
-
* id: "calendar.booking-confirmed",
|
|
14
|
-
* name: "Booking confirmed",
|
|
15
|
-
* trigger: "A guest completes a booking on a public scheduling page.",
|
|
16
|
-
* recipient: "The guest email captured on the booking form.",
|
|
17
|
-
* sender: "EMAIL_FROM, with reply-to set to the event host.",
|
|
18
|
-
* preview: () => renderBookingConfirmedEmail(SAMPLE_BOOKING),
|
|
19
|
-
* });
|
|
20
|
-
*
|
|
21
|
-
* The `id` doubles as the SendGrid category `sendEmail` tags the message with,
|
|
22
|
-
* which is how per-email delivery and open metrics are attributed later.
|
|
23
|
-
*/
|
|
24
|
-
import type { RenderedEmailMessage } from "../server/email-templates.js";
|
|
25
|
-
export interface TransactionalEmailDefinition {
|
|
26
|
-
/**
|
|
27
|
-
* Stable, globally unique id in `<app>.<email>` form, e.g.
|
|
28
|
-
* `calendar.booking-confirmed`. Used as the SendGrid category, so changing it
|
|
29
|
-
* orphans historical metrics for this email.
|
|
30
|
-
*/
|
|
31
|
-
id: string;
|
|
32
|
-
/** Human-readable name, e.g. "Booking confirmed". */
|
|
33
|
-
name: string;
|
|
34
|
-
/**
|
|
35
|
-
* App slug this email belongs to. Defaults to the running app, which is
|
|
36
|
-
* correct for every app-declared email; core system emails set it explicitly.
|
|
37
|
-
*/
|
|
38
|
-
app?: string;
|
|
39
|
-
/** Plain-language description of the condition that causes a send. */
|
|
40
|
-
trigger: string;
|
|
41
|
-
/** Plain-language description of how the recipient address is chosen. */
|
|
42
|
-
recipient: string;
|
|
43
|
-
/**
|
|
44
|
-
* Two-to-four word summary of the recipient for table cells, e.g.
|
|
45
|
-
* "Booking guest". The full `recipient` sentence is shown on the detail view.
|
|
46
|
-
*/
|
|
47
|
-
recipientLabel: string;
|
|
48
|
-
/** Plain-language description of how From and Reply-To are chosen. */
|
|
49
|
-
sender: string;
|
|
50
|
-
/** Two-to-four word summary of the sender, e.g. "Default, reply-to host". */
|
|
51
|
-
senderLabel: string;
|
|
52
|
-
/**
|
|
53
|
-
* Render the email with representative dummy data. Must not read from the
|
|
54
|
-
* database or touch the network — previews are rendered on demand from
|
|
55
|
-
* Dispatch, for apps whose data the caller may not be able to see.
|
|
56
|
-
*/
|
|
57
|
-
preview: () => RenderedEmailMessage;
|
|
58
|
-
}
|
|
59
|
-
/** A definition with its app resolved, as returned to callers. */
|
|
60
|
-
export type RegisteredTransactionalEmail = TransactionalEmailDefinition & {
|
|
61
|
-
app: string;
|
|
62
|
-
};
|
|
63
|
-
/**
|
|
64
|
-
* Register a transactional email. Returns the definition so the call site can
|
|
65
|
-
* export it and reuse `id` when sending.
|
|
66
|
-
*/
|
|
67
|
-
export declare function defineTransactionalEmail(definition: TransactionalEmailDefinition): RegisteredTransactionalEmail;
|
|
68
|
-
/** Every registered email, sorted by app then name. */
|
|
69
|
-
export declare function listTransactionalEmails(): RegisteredTransactionalEmail[];
|
|
70
|
-
export declare function getTransactionalEmail(id: string): RegisteredTransactionalEmail | undefined;
|
|
71
|
-
/**
|
|
72
|
-
* Render one email's preview. Throws when the id is unknown or the renderer
|
|
73
|
-
* fails — a preview that silently returns an empty body would look like an
|
|
74
|
-
* email that legitimately renders blank.
|
|
75
|
-
*/
|
|
76
|
-
export declare function renderTransactionalEmailPreview(id: string): RenderedEmailMessage;
|
|
77
|
-
/** Test seam — drops all registrations. */
|
|
78
|
-
export declare function resetTransactionalEmailRegistry(): void;
|
|
79
|
-
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Registry of the transactional emails an app can send.
|
|
3
|
-
*
|
|
4
|
-
* An app declares each email it sends here so the workspace can answer, without
|
|
5
|
-
* reading the app's source: what emails exist, what makes one send, who it goes
|
|
6
|
-
* to, who it comes from, and what it looks like. Dispatch aggregates these
|
|
7
|
-
* across every mounted app via the `list-transactional-emails` action.
|
|
8
|
-
*
|
|
9
|
-
* Declare emails next to the code that sends them, then import that module from
|
|
10
|
-
* a server plugin so registration happens at startup:
|
|
11
|
-
*
|
|
12
|
-
* defineTransactionalEmail({
|
|
13
|
-
* id: "calendar.booking-confirmed",
|
|
14
|
-
* name: "Booking confirmed",
|
|
15
|
-
* trigger: "A guest completes a booking on a public scheduling page.",
|
|
16
|
-
* recipient: "The guest email captured on the booking form.",
|
|
17
|
-
* sender: "EMAIL_FROM, with reply-to set to the event host.",
|
|
18
|
-
* preview: () => renderBookingConfirmedEmail(SAMPLE_BOOKING),
|
|
19
|
-
* });
|
|
20
|
-
*
|
|
21
|
-
* The `id` doubles as the SendGrid category `sendEmail` tags the message with,
|
|
22
|
-
* which is how per-email delivery and open metrics are attributed later.
|
|
23
|
-
*/
|
|
24
|
-
import { getAppSlug } from "../server/app-name.js";
|
|
25
|
-
const registry = new Map();
|
|
26
|
-
/** Source definitions, so re-registering the same one is a no-op rather than a clash. */
|
|
27
|
-
const sources = new Map();
|
|
28
|
-
/**
|
|
29
|
-
* Register a transactional email. Returns the definition so the call site can
|
|
30
|
-
* export it and reuse `id` when sending.
|
|
31
|
-
*/
|
|
32
|
-
export function defineTransactionalEmail(definition) {
|
|
33
|
-
const existing = sources.get(definition.id);
|
|
34
|
-
if (existing && existing !== definition) {
|
|
35
|
-
// Two emails sharing an id would silently merge their metrics and make the
|
|
36
|
-
// catalog claim one exists when the other actually sent.
|
|
37
|
-
throw new Error(`Duplicate transactional email id "${definition.id}". Ids must be unique across the app.`);
|
|
38
|
-
}
|
|
39
|
-
const resolved = {
|
|
40
|
-
...definition,
|
|
41
|
-
app: definition.app ?? getAppSlug() ?? "unknown",
|
|
42
|
-
};
|
|
43
|
-
registry.set(definition.id, resolved);
|
|
44
|
-
sources.set(definition.id, definition);
|
|
45
|
-
return resolved;
|
|
46
|
-
}
|
|
47
|
-
/** Every registered email, sorted by app then name. */
|
|
48
|
-
export function listTransactionalEmails() {
|
|
49
|
-
return [...registry.values()].sort((a, b) => a.app.localeCompare(b.app) || a.name.localeCompare(b.name));
|
|
50
|
-
}
|
|
51
|
-
export function getTransactionalEmail(id) {
|
|
52
|
-
return registry.get(id);
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Render one email's preview. Throws when the id is unknown or the renderer
|
|
56
|
-
* fails — a preview that silently returns an empty body would look like an
|
|
57
|
-
* email that legitimately renders blank.
|
|
58
|
-
*/
|
|
59
|
-
export function renderTransactionalEmailPreview(id) {
|
|
60
|
-
const definition = registry.get(id);
|
|
61
|
-
if (!definition) {
|
|
62
|
-
throw new Error(`Unknown transactional email "${id}".`);
|
|
63
|
-
}
|
|
64
|
-
return definition.preview();
|
|
65
|
-
}
|
|
66
|
-
/** Test seam — drops all registrations. */
|
|
67
|
-
export function resetTransactionalEmailRegistry() {
|
|
68
|
-
registry.clear();
|
|
69
|
-
sources.clear();
|
|
70
|
-
}
|
|
71
|
-
//# sourceMappingURL=registry.js.map
|
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Drizzle schema for the transactional email send log.
|
|
3
|
-
*
|
|
4
|
-
* One row per `sendEmail` attempt, written by the transport itself so every
|
|
5
|
-
* send is recorded regardless of which app or code path triggered it. This is
|
|
6
|
-
* the durable record of "did we send it": the provider's own activity feed ages
|
|
7
|
-
* out (SendGrid keeps 3 days without the extended-retention add-on), so send
|
|
8
|
-
* counts and last-sent must not depend on it.
|
|
9
|
-
*
|
|
10
|
-
* Engagement (opens, clicks, bounces) is deliberately NOT stored here. Only the
|
|
11
|
-
* provider knows it, and mirroring it would go stale the moment a recipient
|
|
12
|
-
* opens an old message. Dispatch reads engagement live from the provider and
|
|
13
|
-
* joins on `template_id`.
|
|
14
|
-
*/
|
|
15
|
-
export declare const emailLog: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
|
|
16
|
-
name: "email_log";
|
|
17
|
-
schema: undefined;
|
|
18
|
-
columns: {
|
|
19
|
-
id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
20
|
-
name: "id";
|
|
21
|
-
tableName: "email_log";
|
|
22
|
-
dataType: "string";
|
|
23
|
-
columnType: "SQLiteText";
|
|
24
|
-
data: string;
|
|
25
|
-
driverParam: string;
|
|
26
|
-
notNull: true;
|
|
27
|
-
hasDefault: false;
|
|
28
|
-
isPrimaryKey: true;
|
|
29
|
-
isAutoincrement: false;
|
|
30
|
-
hasRuntimeDefault: false;
|
|
31
|
-
enumValues: [string, ...string[]];
|
|
32
|
-
baseColumn: never;
|
|
33
|
-
identity: undefined;
|
|
34
|
-
generated: undefined;
|
|
35
|
-
}, {}, {
|
|
36
|
-
length: number;
|
|
37
|
-
}>;
|
|
38
|
-
templateId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
39
|
-
name: "template_id";
|
|
40
|
-
tableName: "email_log";
|
|
41
|
-
dataType: "string";
|
|
42
|
-
columnType: "SQLiteText";
|
|
43
|
-
data: string;
|
|
44
|
-
driverParam: string;
|
|
45
|
-
notNull: false;
|
|
46
|
-
hasDefault: false;
|
|
47
|
-
isPrimaryKey: false;
|
|
48
|
-
isAutoincrement: false;
|
|
49
|
-
hasRuntimeDefault: false;
|
|
50
|
-
enumValues: [string, ...string[]];
|
|
51
|
-
baseColumn: never;
|
|
52
|
-
identity: undefined;
|
|
53
|
-
generated: undefined;
|
|
54
|
-
}, {}, {
|
|
55
|
-
length: number;
|
|
56
|
-
}>;
|
|
57
|
-
app: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
58
|
-
name: "app";
|
|
59
|
-
tableName: "email_log";
|
|
60
|
-
dataType: "string";
|
|
61
|
-
columnType: "SQLiteText";
|
|
62
|
-
data: string;
|
|
63
|
-
driverParam: string;
|
|
64
|
-
notNull: false;
|
|
65
|
-
hasDefault: false;
|
|
66
|
-
isPrimaryKey: false;
|
|
67
|
-
isAutoincrement: false;
|
|
68
|
-
hasRuntimeDefault: false;
|
|
69
|
-
enumValues: [string, ...string[]];
|
|
70
|
-
baseColumn: never;
|
|
71
|
-
identity: undefined;
|
|
72
|
-
generated: undefined;
|
|
73
|
-
}, {}, {
|
|
74
|
-
length: number;
|
|
75
|
-
}>;
|
|
76
|
-
recipient: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
77
|
-
name: "recipient";
|
|
78
|
-
tableName: "email_log";
|
|
79
|
-
dataType: "string";
|
|
80
|
-
columnType: "SQLiteText";
|
|
81
|
-
data: string;
|
|
82
|
-
driverParam: string;
|
|
83
|
-
notNull: true;
|
|
84
|
-
hasDefault: false;
|
|
85
|
-
isPrimaryKey: false;
|
|
86
|
-
isAutoincrement: false;
|
|
87
|
-
hasRuntimeDefault: false;
|
|
88
|
-
enumValues: [string, ...string[]];
|
|
89
|
-
baseColumn: never;
|
|
90
|
-
identity: undefined;
|
|
91
|
-
generated: undefined;
|
|
92
|
-
}, {}, {
|
|
93
|
-
length: number;
|
|
94
|
-
}>;
|
|
95
|
-
sender: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
96
|
-
name: "sender";
|
|
97
|
-
tableName: "email_log";
|
|
98
|
-
dataType: "string";
|
|
99
|
-
columnType: "SQLiteText";
|
|
100
|
-
data: string;
|
|
101
|
-
driverParam: string;
|
|
102
|
-
notNull: true;
|
|
103
|
-
hasDefault: false;
|
|
104
|
-
isPrimaryKey: false;
|
|
105
|
-
isAutoincrement: false;
|
|
106
|
-
hasRuntimeDefault: false;
|
|
107
|
-
enumValues: [string, ...string[]];
|
|
108
|
-
baseColumn: never;
|
|
109
|
-
identity: undefined;
|
|
110
|
-
generated: undefined;
|
|
111
|
-
}, {}, {
|
|
112
|
-
length: number;
|
|
113
|
-
}>;
|
|
114
|
-
subject: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
115
|
-
name: "subject";
|
|
116
|
-
tableName: "email_log";
|
|
117
|
-
dataType: "string";
|
|
118
|
-
columnType: "SQLiteText";
|
|
119
|
-
data: string;
|
|
120
|
-
driverParam: string;
|
|
121
|
-
notNull: true;
|
|
122
|
-
hasDefault: false;
|
|
123
|
-
isPrimaryKey: false;
|
|
124
|
-
isAutoincrement: false;
|
|
125
|
-
hasRuntimeDefault: false;
|
|
126
|
-
enumValues: [string, ...string[]];
|
|
127
|
-
baseColumn: never;
|
|
128
|
-
identity: undefined;
|
|
129
|
-
generated: undefined;
|
|
130
|
-
}, {}, {
|
|
131
|
-
length: number;
|
|
132
|
-
}>;
|
|
133
|
-
status: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
134
|
-
name: "status";
|
|
135
|
-
tableName: "email_log";
|
|
136
|
-
dataType: "string";
|
|
137
|
-
columnType: "SQLiteText";
|
|
138
|
-
data: "failed" | "sent";
|
|
139
|
-
driverParam: string;
|
|
140
|
-
notNull: true;
|
|
141
|
-
hasDefault: false;
|
|
142
|
-
isPrimaryKey: false;
|
|
143
|
-
isAutoincrement: false;
|
|
144
|
-
hasRuntimeDefault: false;
|
|
145
|
-
enumValues: ["sent", "failed"];
|
|
146
|
-
baseColumn: never;
|
|
147
|
-
identity: undefined;
|
|
148
|
-
generated: undefined;
|
|
149
|
-
}, {}, {
|
|
150
|
-
length: number;
|
|
151
|
-
}>;
|
|
152
|
-
error: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
153
|
-
name: "error";
|
|
154
|
-
tableName: "email_log";
|
|
155
|
-
dataType: "string";
|
|
156
|
-
columnType: "SQLiteText";
|
|
157
|
-
data: string;
|
|
158
|
-
driverParam: string;
|
|
159
|
-
notNull: false;
|
|
160
|
-
hasDefault: false;
|
|
161
|
-
isPrimaryKey: false;
|
|
162
|
-
isAutoincrement: false;
|
|
163
|
-
hasRuntimeDefault: false;
|
|
164
|
-
enumValues: [string, ...string[]];
|
|
165
|
-
baseColumn: never;
|
|
166
|
-
identity: undefined;
|
|
167
|
-
generated: undefined;
|
|
168
|
-
}, {}, {
|
|
169
|
-
length: number;
|
|
170
|
-
}>;
|
|
171
|
-
provider: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
172
|
-
name: "provider";
|
|
173
|
-
tableName: "email_log";
|
|
174
|
-
dataType: "string";
|
|
175
|
-
columnType: "SQLiteText";
|
|
176
|
-
data: string;
|
|
177
|
-
driverParam: string;
|
|
178
|
-
notNull: true;
|
|
179
|
-
hasDefault: false;
|
|
180
|
-
isPrimaryKey: false;
|
|
181
|
-
isAutoincrement: false;
|
|
182
|
-
hasRuntimeDefault: false;
|
|
183
|
-
enumValues: [string, ...string[]];
|
|
184
|
-
baseColumn: never;
|
|
185
|
-
identity: undefined;
|
|
186
|
-
generated: undefined;
|
|
187
|
-
}, {}, {
|
|
188
|
-
length: number;
|
|
189
|
-
}>;
|
|
190
|
-
createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
191
|
-
name: "created_at";
|
|
192
|
-
tableName: "email_log";
|
|
193
|
-
dataType: "number";
|
|
194
|
-
columnType: "SQLiteInteger";
|
|
195
|
-
data: number;
|
|
196
|
-
driverParam: number;
|
|
197
|
-
notNull: true;
|
|
198
|
-
hasDefault: false;
|
|
199
|
-
isPrimaryKey: false;
|
|
200
|
-
isAutoincrement: false;
|
|
201
|
-
hasRuntimeDefault: false;
|
|
202
|
-
enumValues: undefined;
|
|
203
|
-
baseColumn: never;
|
|
204
|
-
identity: undefined;
|
|
205
|
-
generated: undefined;
|
|
206
|
-
}, {}, {}>;
|
|
207
|
-
};
|
|
208
|
-
dialect: 'sqlite';
|
|
209
|
-
}>;
|
|
210
|
-
export declare const EMAIL_LOG_CREATE_SQL = "CREATE TABLE IF NOT EXISTS email_log (\n id TEXT PRIMARY KEY,\n template_id TEXT,\n app TEXT,\n recipient TEXT NOT NULL,\n sender TEXT NOT NULL,\n subject TEXT NOT NULL,\n status TEXT NOT NULL,\n error TEXT,\n provider TEXT NOT NULL,\n created_at INTEGER NOT NULL\n)";
|
|
211
|
-
export declare const EMAIL_LOG_TEMPLATE_INDEX_SQL = "CREATE INDEX IF NOT EXISTS email_log_template_created_idx\n ON email_log (template_id, created_at)";
|
|
212
|
-
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Drizzle schema for the transactional email send log.
|
|
3
|
-
*
|
|
4
|
-
* One row per `sendEmail` attempt, written by the transport itself so every
|
|
5
|
-
* send is recorded regardless of which app or code path triggered it. This is
|
|
6
|
-
* the durable record of "did we send it": the provider's own activity feed ages
|
|
7
|
-
* out (SendGrid keeps 3 days without the extended-retention add-on), so send
|
|
8
|
-
* counts and last-sent must not depend on it.
|
|
9
|
-
*
|
|
10
|
-
* Engagement (opens, clicks, bounces) is deliberately NOT stored here. Only the
|
|
11
|
-
* provider knows it, and mirroring it would go stale the moment a recipient
|
|
12
|
-
* opens an old message. Dispatch reads engagement live from the provider and
|
|
13
|
-
* joins on `template_id`.
|
|
14
|
-
*/
|
|
15
|
-
import { table, text, integer } from "../db/schema.js";
|
|
16
|
-
export const emailLog = table("email_log", {
|
|
17
|
-
id: text("id").primaryKey(),
|
|
18
|
-
/** Registered transactional email id, e.g. "calendar.booking-confirmed". */
|
|
19
|
-
templateId: text("template_id"),
|
|
20
|
-
/** App slug that sent it. */
|
|
21
|
-
app: text("app"),
|
|
22
|
-
/** Recipient address. */
|
|
23
|
-
recipient: text("recipient").notNull(),
|
|
24
|
-
/** Resolved From address, after app-sender branding is applied. */
|
|
25
|
-
sender: text("sender").notNull(),
|
|
26
|
-
subject: text("subject").notNull(),
|
|
27
|
-
/** "sent" once the provider accepted it, or "failed". Never optimistic. */
|
|
28
|
-
status: text("status", { enum: ["sent", "failed"] }).notNull(),
|
|
29
|
-
/** Provider error text when status is "failed". */
|
|
30
|
-
error: text("error"),
|
|
31
|
-
/** "resend" | "sendgrid" | "dev". */
|
|
32
|
-
provider: text("provider").notNull(),
|
|
33
|
-
createdAt: integer("created_at").notNull(),
|
|
34
|
-
});
|
|
35
|
-
export const EMAIL_LOG_CREATE_SQL = `CREATE TABLE IF NOT EXISTS email_log (
|
|
36
|
-
id TEXT PRIMARY KEY,
|
|
37
|
-
template_id TEXT,
|
|
38
|
-
app TEXT,
|
|
39
|
-
recipient TEXT NOT NULL,
|
|
40
|
-
sender TEXT NOT NULL,
|
|
41
|
-
subject TEXT NOT NULL,
|
|
42
|
-
status TEXT NOT NULL,
|
|
43
|
-
error TEXT,
|
|
44
|
-
provider TEXT NOT NULL,
|
|
45
|
-
created_at INTEGER NOT NULL
|
|
46
|
-
)`;
|
|
47
|
-
export const EMAIL_LOG_TEMPLATE_INDEX_SQL = `CREATE INDEX IF NOT EXISTS email_log_template_created_idx
|
|
48
|
-
ON email_log (template_id, created_at)`;
|
|
49
|
-
//# sourceMappingURL=schema.js.map
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Catalog entries for the framework's own system emails.
|
|
3
|
-
*
|
|
4
|
-
* These ship with every app rather than belonging to one, so they register
|
|
5
|
-
* under the `core` app instead of the running app's slug. Importing this module
|
|
6
|
-
* performs the registration; `register-system-emails.ts` is the single import
|
|
7
|
-
* site so the emails appear in every app's catalog without each template
|
|
8
|
-
* remembering to opt in.
|
|
9
|
-
*/
|
|
10
|
-
export declare const CORE_INVITE_EMAIL_ID = "core.organization-invite";
|
|
11
|
-
export declare const CORE_VERIFY_SIGNUP_EMAIL_ID = "core.verify-signup";
|
|
12
|
-
export declare const CORE_RESET_PASSWORD_EMAIL_ID = "core.reset-password";
|
|
13
|
-
export declare function registerCoreSystemEmails(): void;
|
|
14
|
-
//# sourceMappingURL=system-emails.d.ts.map
|