@hogsend/engine 0.12.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hogsend/engine",
3
- "version": "0.12.0",
3
+ "version": "0.12.1",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -40,14 +40,14 @@
40
40
  "svix": "^1.95.1",
41
41
  "winston": "^3.19.0",
42
42
  "zod": "^4.4.3",
43
- "@hogsend/core": "^0.12.0",
44
- "@hogsend/db": "^0.12.0",
45
- "@hogsend/email": "^0.12.0",
46
- "@hogsend/plugin-posthog": "^0.12.0",
47
- "@hogsend/plugin-resend": "^0.12.0"
43
+ "@hogsend/core": "^0.12.1",
44
+ "@hogsend/db": "^0.12.1",
45
+ "@hogsend/email": "^0.12.1",
46
+ "@hogsend/plugin-posthog": "^0.12.1",
47
+ "@hogsend/plugin-resend": "^0.12.1"
48
48
  },
49
49
  "optionalDependencies": {
50
- "@hogsend/plugin-postmark": "^0.12.0"
50
+ "@hogsend/plugin-postmark": "^0.12.1"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/node": "^22.15.3",
@@ -59,6 +59,23 @@ const EngineDomainStatusSchema = z.object({
59
59
  /** Pinned domain validation regex (PROJECT_SPEC §e). */
60
60
  const DOMAIN_RE = /^([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$/i;
61
61
 
62
+ /**
63
+ * Provider domains calls can fail for configuration reasons the operator must
64
+ * act on (e.g. a send-only restricted Resend key cannot read the domains API).
65
+ * Surface those as 502 with the provider's message — `hogsend domain` and
66
+ * Studio render this string directly — instead of an opaque 500.
67
+ */
68
+ const providerErrorBody = (providerId: string, err: unknown) => ({
69
+ error: `domains request to provider "${providerId}" failed: ${
70
+ err instanceof Error ? err.message : String(err)
71
+ }`,
72
+ });
73
+
74
+ const providerErrorResponse = {
75
+ content: { "application/json": { schema: errorSchema } },
76
+ description: "The provider rejected or failed the domains request",
77
+ } as const;
78
+
62
79
  const getDomainRoute = createRoute({
63
80
  method: "get",
64
81
  path: "/",
@@ -77,6 +94,7 @@ const getDomainRoute = createRoute({
77
94
  description:
78
95
  "Cached domain status for the active email provider; ?refresh=true forces a provider round-trip",
79
96
  },
97
+ 502: providerErrorResponse,
80
98
  },
81
99
  });
82
100
 
@@ -107,6 +125,7 @@ const addDomainRoute = createRoute({
107
125
  content: { "application/json": { schema: errorSchema } },
108
126
  description: "The active provider has no domains capability",
109
127
  },
128
+ 502: providerErrorResponse,
110
129
  },
111
130
  });
112
131
 
@@ -130,15 +149,23 @@ const verifyDomainRoute = createRoute({
130
149
  content: { "application/json": { schema: errorSchema } },
131
150
  description: "The active provider has no domains capability",
132
151
  },
152
+ 502: providerErrorResponse,
133
153
  },
134
154
  });
135
155
 
136
156
  export const domainRouter = new OpenAPIHono<AppEnv>()
137
157
  .openapi(getDomainRoute, async (c) => {
138
- const { domainStatus } = c.get("container");
158
+ const { domainStatus, emailProvider } = c.get("container");
139
159
  const { refresh } = c.req.valid("query");
140
- const status = await domainStatus.getStatus({ refresh });
141
- return c.json(status, 200);
160
+ try {
161
+ const status = await domainStatus.getStatus({ refresh });
162
+ return c.json(status, 200);
163
+ } catch (err) {
164
+ return c.json(
165
+ providerErrorBody(emailProvider.meta?.id ?? "email", err),
166
+ 502,
167
+ );
168
+ }
142
169
  })
143
170
  .openapi(addDomainRoute, async (c) => {
144
171
  const { domainStatus, emailProvider } = c.get("container");
@@ -148,12 +175,19 @@ export const domainRouter = new OpenAPIHono<AppEnv>()
148
175
  return c.json({ error: "provider_unsupported" }, 501);
149
176
  }
150
177
 
151
- // Idempotent at the provider (an existing domain falls through to lookup).
152
- await emailProvider.domains.create(domain);
153
-
154
- // Bust + refresh the cached snapshot so the response reflects the create.
155
- const status = await domainStatus.getStatus({ refresh: true });
156
- return c.json(status, 200);
178
+ try {
179
+ // Idempotent at the provider (an existing domain falls through to lookup).
180
+ await emailProvider.domains.create(domain);
181
+
182
+ // Bust + refresh the cached snapshot so the response reflects the create.
183
+ const status = await domainStatus.getStatus({ refresh: true });
184
+ return c.json(status, 200);
185
+ } catch (err) {
186
+ return c.json(
187
+ providerErrorBody(emailProvider.meta?.id ?? "email", err),
188
+ 502,
189
+ );
190
+ }
157
191
  })
158
192
  .openapi(verifyDomainRoute, async (c) => {
159
193
  const { domainStatus, emailProvider } = c.get("container");
@@ -162,20 +196,27 @@ export const domainRouter = new OpenAPIHono<AppEnv>()
162
196
  return c.json({ error: "provider_unsupported" }, 501);
163
197
  }
164
198
 
165
- const current = await domainStatus.getStatus();
166
- if (!current.domain) {
167
- return c.json({ error: "no_domain_configured" }, 400);
199
+ try {
200
+ const current = await domainStatus.getStatus();
201
+ if (!current.domain) {
202
+ return c.json({ error: "no_domain_configured" }, 400);
203
+ }
204
+
205
+ // Prefer the provider's explicit verification pass; fall back to a plain
206
+ // status fetch for providers without one.
207
+ const capability = emailProvider.domains;
208
+ if (capability.verify) {
209
+ await capability.verify(current.domain);
210
+ } else {
211
+ await capability.get(current.domain);
212
+ }
213
+
214
+ const status = await domainStatus.getStatus({ refresh: true });
215
+ return c.json(status, 200);
216
+ } catch (err) {
217
+ return c.json(
218
+ providerErrorBody(emailProvider.meta?.id ?? "email", err),
219
+ 502,
220
+ );
168
221
  }
169
-
170
- // Prefer the provider's explicit verification pass; fall back to a plain
171
- // status fetch for providers without one.
172
- const capability = emailProvider.domains;
173
- if (capability.verify) {
174
- await capability.verify(current.domain);
175
- } else {
176
- await capability.get(current.domain);
177
- }
178
-
179
- const status = await domainStatus.getStatus({ refresh: true });
180
- return c.json(status, 200);
181
222
  });