@beignet/cli 0.0.36 → 0.0.38
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/CHANGELOG.md +27 -0
- package/README.md +36 -5
- package/dist/config.d.ts +15 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +30 -1
- package/dist/config.js.map +1 -1
- package/dist/inspect.js +21 -17
- package/dist/inspect.js.map +1 -1
- package/dist/lint.d.ts.map +1 -1
- package/dist/lint.js +27 -6
- package/dist/lint.js.map +1 -1
- package/dist/make/inbox.js +7 -7
- package/dist/make/inbox.js.map +1 -1
- package/dist/make/payments.d.ts +2 -1
- package/dist/make/payments.d.ts.map +1 -1
- package/dist/make/payments.js +15 -11
- package/dist/make/payments.js.map +1 -1
- package/dist/make/shared.d.ts +1 -1
- package/dist/make/shared.d.ts.map +1 -1
- package/dist/make/shared.js +49 -37
- package/dist/make/shared.js.map +1 -1
- package/dist/make/tenancy.js +8 -8
- package/dist/make/tenancy.js.map +1 -1
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +27 -16
- package/dist/make.js.map +1 -1
- package/dist/operational-error-reporting.d.ts +15 -0
- package/dist/operational-error-reporting.d.ts.map +1 -0
- package/dist/operational-error-reporting.js +42 -0
- package/dist/operational-error-reporting.js.map +1 -0
- package/dist/outbox.d.ts.map +1 -1
- package/dist/outbox.js +73 -0
- package/dist/outbox.js.map +1 -1
- package/dist/provider-add.js +17 -17
- package/dist/provider-add.js.map +1 -1
- package/dist/provider-audit.d.ts.map +1 -1
- package/dist/provider-audit.js +11 -6
- package/dist/provider-audit.js.map +1 -1
- package/dist/schedule.d.ts.map +1 -1
- package/dist/schedule.js +28 -1
- package/dist/schedule.js.map +1 -1
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js +37 -4
- package/dist/task.js.map +1 -1
- package/dist/templates/base.d.ts.map +1 -1
- package/dist/templates/base.js +6 -1
- package/dist/templates/base.js.map +1 -1
- package/dist/templates/db/index.d.ts +1 -0
- package/dist/templates/db/index.d.ts.map +1 -1
- package/dist/templates/db/index.js.map +1 -1
- package/dist/templates/db/mysql.d.ts.map +1 -1
- package/dist/templates/db/mysql.js +16 -0
- package/dist/templates/db/mysql.js.map +1 -1
- package/dist/templates/db/postgres.d.ts.map +1 -1
- package/dist/templates/db/postgres.js +16 -0
- package/dist/templates/db/postgres.js.map +1 -1
- package/dist/templates/db/sqlite.d.ts.map +1 -1
- package/dist/templates/db/sqlite.js +16 -0
- package/dist/templates/db/sqlite.js.map +1 -1
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +4 -3
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/server.d.ts +2 -2
- package/dist/templates/server.d.ts.map +1 -1
- package/dist/templates/server.js +56 -40
- package/dist/templates/server.js.map +1 -1
- package/dist/templates/shared.js +3 -3
- package/dist/templates/shared.js.map +1 -1
- package/dist/templates/testing.js +1 -1
- package/dist/templates/todos.js +6 -6
- package/package.json +4 -4
- package/src/config.ts +66 -2
- package/src/inspect.ts +31 -19
- package/src/lint.ts +35 -4
- package/src/make/inbox.ts +7 -7
- package/src/make/payments.ts +23 -15
- package/src/make/shared.ts +56 -40
- package/src/make/tenancy.ts +8 -8
- package/src/make.ts +30 -16
- package/src/operational-error-reporting.ts +60 -0
- package/src/outbox.ts +77 -0
- package/src/provider-add.ts +21 -17
- package/src/provider-audit.ts +28 -11
- package/src/schedule.ts +32 -1
- package/src/task.ts +41 -4
- package/src/templates/base.ts +6 -1
- package/src/templates/db/index.ts +1 -0
- package/src/templates/db/mysql.ts +16 -0
- package/src/templates/db/postgres.ts +16 -0
- package/src/templates/db/sqlite.ts +16 -0
- package/src/templates/index.ts +4 -3
- package/src/templates/server.ts +56 -42
- package/src/templates/shared.ts +3 -3
- package/src/templates/testing.ts +1 -1
- package/src/templates/todos.ts +6 -6
package/src/outbox.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile, stat } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import type { ErrorReporterPort } from "@beignet/core/error-reporting";
|
|
3
4
|
import type {
|
|
4
5
|
DrainOutboxOptions,
|
|
5
6
|
DrainOutboxResult,
|
|
@@ -11,6 +12,10 @@ import type {
|
|
|
11
12
|
} from "@beignet/core/outbox";
|
|
12
13
|
import { createJiti } from "jiti";
|
|
13
14
|
import { loadBeignetConfig, normalizePath } from "./config.js";
|
|
15
|
+
import {
|
|
16
|
+
flushOperationalErrorReporter,
|
|
17
|
+
reportOperationalFailure,
|
|
18
|
+
} from "./operational-error-reporting.js";
|
|
14
19
|
|
|
15
20
|
/**
|
|
16
21
|
* Options for running one bounded outbox drain pass.
|
|
@@ -159,6 +164,7 @@ type OutboxDrainPorts = {
|
|
|
159
164
|
logger?: OutboxDrainLogger;
|
|
160
165
|
devtools?: OutboxDrainInstrumentation;
|
|
161
166
|
instrumentation?: OutboxDrainInstrumentation;
|
|
167
|
+
errorReporter?: ErrorReporterPort;
|
|
162
168
|
};
|
|
163
169
|
|
|
164
170
|
type OutboxDrainContext = {
|
|
@@ -240,6 +246,60 @@ export async function runOutboxDrain(
|
|
|
240
246
|
attempts: message.attempts,
|
|
241
247
|
});
|
|
242
248
|
},
|
|
249
|
+
onDeadLetter(error, message) {
|
|
250
|
+
return reportOperationalFailure({
|
|
251
|
+
ctx,
|
|
252
|
+
error,
|
|
253
|
+
reportOptions: {
|
|
254
|
+
level: "error",
|
|
255
|
+
mechanism: "beignet.outbox.cli",
|
|
256
|
+
handled: false,
|
|
257
|
+
tags: {
|
|
258
|
+
"beignet.kind": "outbox",
|
|
259
|
+
"beignet.outbox.kind": message.kind,
|
|
260
|
+
"beignet.outbox.name": message.name,
|
|
261
|
+
"beignet.outbox.outcome": "deadLettered",
|
|
262
|
+
},
|
|
263
|
+
contexts: {
|
|
264
|
+
outbox: {
|
|
265
|
+
messageId: message.id,
|
|
266
|
+
kind: message.kind,
|
|
267
|
+
name: message.name,
|
|
268
|
+
attempts: message.attempts,
|
|
269
|
+
maxAttempts: message.maxAttempts,
|
|
270
|
+
outcome: "deadLettered",
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
},
|
|
276
|
+
onSettlementError(settlementError, message) {
|
|
277
|
+
return reportOperationalFailure({
|
|
278
|
+
ctx,
|
|
279
|
+
error: settlementError,
|
|
280
|
+
reportOptions: {
|
|
281
|
+
level: "error",
|
|
282
|
+
mechanism: "beignet.outbox.cli",
|
|
283
|
+
handled: false,
|
|
284
|
+
tags: {
|
|
285
|
+
"beignet.kind": "outbox",
|
|
286
|
+
"beignet.outbox.kind": message.kind,
|
|
287
|
+
"beignet.outbox.name": message.name,
|
|
288
|
+
"beignet.outbox.outcome": "settlementFailed",
|
|
289
|
+
},
|
|
290
|
+
contexts: {
|
|
291
|
+
outbox: {
|
|
292
|
+
messageId: message.id,
|
|
293
|
+
kind: message.kind,
|
|
294
|
+
name: message.name,
|
|
295
|
+
attempts: message.attempts,
|
|
296
|
+
maxAttempts: message.maxAttempts,
|
|
297
|
+
outcome: "settlementFailed",
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
},
|
|
243
303
|
});
|
|
244
304
|
|
|
245
305
|
await recordOutboxDrain(ctx, result);
|
|
@@ -251,7 +311,24 @@ export async function runOutboxDrain(
|
|
|
251
311
|
result,
|
|
252
312
|
durationMs: Math.round(performance.now() - startedAt),
|
|
253
313
|
};
|
|
314
|
+
} catch (error) {
|
|
315
|
+
await reportOperationalFailure({
|
|
316
|
+
ctx,
|
|
317
|
+
error,
|
|
318
|
+
reportOptions: {
|
|
319
|
+
level: "error",
|
|
320
|
+
mechanism: "beignet.outbox.cli",
|
|
321
|
+
handled: false,
|
|
322
|
+
tags: {
|
|
323
|
+
"beignet.kind": "outbox",
|
|
324
|
+
"beignet.outbox.outcome": "drainFailed",
|
|
325
|
+
},
|
|
326
|
+
contexts: { outbox: { outcome: "drainFailed" } },
|
|
327
|
+
},
|
|
328
|
+
});
|
|
329
|
+
throw error;
|
|
254
330
|
} finally {
|
|
331
|
+
await flushOperationalErrorReporter(ctx);
|
|
255
332
|
await outboxModule.stopOutboxDrainContext?.(ctx, contextArgs);
|
|
256
333
|
}
|
|
257
334
|
}
|
package/src/provider-add.ts
CHANGED
|
@@ -97,7 +97,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
97
97
|
"",
|
|
98
98
|
"- Package: `@beignet/provider-flags-openfeature`",
|
|
99
99
|
"- Peer dependency: `@openfeature/server-sdk`",
|
|
100
|
-
"- The preset wires `createOpenFeatureFlagsProvider()` in `server/providers.ts` and defers `flags` in `infra/
|
|
100
|
+
"- The preset wires `createOpenFeatureFlagsProvider()` in `server/providers.ts` and defers `flags` in `infra/port-wiring.ts`.",
|
|
101
101
|
"- Configure the OpenFeature provider globally or replace this preset with `createOpenFeatureFlagsProvider(...)` when the app needs provider-specific setup.",
|
|
102
102
|
"- Use `ctx.ports.flags` in app code; `ctx.ports.openFeature` is available as an escape hatch.",
|
|
103
103
|
"",
|
|
@@ -138,7 +138,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
138
138
|
"",
|
|
139
139
|
"- Package: `@beignet/provider-jobs-bullmq`",
|
|
140
140
|
"- Peer dependency: `bullmq`",
|
|
141
|
-
"- The preset wires `createBullMQJobsProvider()` in `server/providers.ts` and defers `jobs` in `infra/
|
|
141
|
+
"- The preset wires `createBullMQJobsProvider()` in `server/providers.ts` and defers `jobs` in `infra/port-wiring.ts`.",
|
|
142
142
|
"- Set `BULLMQ_REDIS_URL` before booting the app; the default points at local Redis.",
|
|
143
143
|
"- Dispatching enqueues jobs, but nothing executes them until a worker entrypoint runs `createBullMQJobWorker(...)` in a worker process. See the jobs docs page.",
|
|
144
144
|
"- Use `ctx.ports.jobs.dispatch(...)` in app code; `ctx.ports.bullMQJobs` is available as an escape hatch.",
|
|
@@ -180,7 +180,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
180
180
|
"",
|
|
181
181
|
"- Package: `@beignet/provider-jobs-inngest`",
|
|
182
182
|
"- Peer dependency: `inngest`",
|
|
183
|
-
"- The preset wires `createInngestJobsProvider()` in `server/providers.ts` and defers `jobs` in `infra/
|
|
183
|
+
"- The preset wires `createInngestJobsProvider()` in `server/providers.ts` and defers `jobs` in `infra/port-wiring.ts`.",
|
|
184
184
|
"- Define jobs with `defineJob(...)` and dispatch them through `ctx.ports.jobs.dispatch(...)`.",
|
|
185
185
|
"- Execution needs an Inngest serve route built from `createInngestJobFunction(...)` and `serve(...)`. See the jobs docs page.",
|
|
186
186
|
"- Use `ctx.ports.inngest` only as an escape hatch for Inngest-specific workflow APIs; prefer `ctx.ports.jobs.dispatch(...)` for Beignet jobs.",
|
|
@@ -222,7 +222,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
222
222
|
"",
|
|
223
223
|
"- Package: `@beignet/provider-mail-resend`",
|
|
224
224
|
"- Peer dependency: `resend`",
|
|
225
|
-
"- The preset wires `createResendMailProvider()` in `server/providers.ts` and defers `mailer` in `infra/
|
|
225
|
+
"- The preset wires `createResendMailProvider()` in `server/providers.ts` and defers `mailer` in `infra/port-wiring.ts`.",
|
|
226
226
|
"- `.env.example` ships dev placeholders so the app boots; set real `RESEND_API_KEY` and `RESEND_FROM` values before sending mail.",
|
|
227
227
|
"- Use `ctx.ports.mailer` in app code; `ctx.ports.resend` is available as an escape hatch.",
|
|
228
228
|
"",
|
|
@@ -268,7 +268,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
268
268
|
"",
|
|
269
269
|
"- Package: `@beignet/provider-mail-smtp`",
|
|
270
270
|
"- Peer dependency: `nodemailer`",
|
|
271
|
-
"- The preset wires `createSmtpMailProvider()` in `server/providers.ts` and defers `mailer` in `infra/
|
|
271
|
+
"- The preset wires `createSmtpMailProvider()` in `server/providers.ts` and defers `mailer` in `infra/port-wiring.ts`.",
|
|
272
272
|
"- `.env.example` ships dev placeholders so the app boots; set real `MAIL_*` values before sending mail.",
|
|
273
273
|
"- Use `ctx.ports.mailer` in app code; `ctx.ports.smtp` is available as an escape hatch.",
|
|
274
274
|
"",
|
|
@@ -317,7 +317,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
317
317
|
"",
|
|
318
318
|
"- Package: `@beignet/provider-payments-stripe`",
|
|
319
319
|
"- Peer dependency: `stripe`",
|
|
320
|
-
"- The preset wires `createStripePaymentsProvider()` in `server/providers.ts` and defers `payments` in `infra/
|
|
320
|
+
"- The preset wires `createStripePaymentsProvider()` in `server/providers.ts` and defers `payments` in `infra/port-wiring.ts`.",
|
|
321
321
|
"- `.env.example` ships dev placeholders so the app boots; set real `STRIPE_SECRET_KEY` and `STRIPE_WEBHOOK_SECRET` values before taking payments.",
|
|
322
322
|
"- Stripe requires a webhook route: run `beignet make payments` for the full billing slice, including a `createPaymentWebhookRoute(...)` route.",
|
|
323
323
|
"- Use `ctx.ports.payments` in app code; `ctx.ports.stripe` is available as an escape hatch.",
|
|
@@ -360,7 +360,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
360
360
|
"## Meilisearch search",
|
|
361
361
|
"",
|
|
362
362
|
"- Package: `@beignet/provider-search-meilisearch`",
|
|
363
|
-
"- The preset wires `createMeilisearchSearchProvider()` in `server/providers.ts` and defers `search` in `infra/
|
|
363
|
+
"- The preset wires `createMeilisearchSearchProvider()` in `server/providers.ts` and defers `search` in `infra/port-wiring.ts`.",
|
|
364
364
|
"- Set `MEILISEARCH_HOST` before booting the app; set `MEILISEARCH_API_KEY` when your Meilisearch server requires one.",
|
|
365
365
|
"- Use `ctx.ports.search` in app code; `ctx.ports.meilisearch` is available as an escape hatch.",
|
|
366
366
|
"",
|
|
@@ -434,7 +434,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
434
434
|
"",
|
|
435
435
|
"- Package: `@beignet/provider-cache-redis`",
|
|
436
436
|
"- Peer dependency: `ioredis`",
|
|
437
|
-
"- The preset wires `createRedisCacheProvider()` in `server/providers.ts` and defers `cache` in `infra/
|
|
437
|
+
"- The preset wires `createRedisCacheProvider()` in `server/providers.ts` and defers `cache` in `infra/port-wiring.ts`.",
|
|
438
438
|
"- Set `REDIS_URL` before booting the app.",
|
|
439
439
|
"- Use `ctx.ports.cache` in app code; `ctx.ports.redis` is available as an escape hatch.",
|
|
440
440
|
"",
|
|
@@ -475,7 +475,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
475
475
|
"",
|
|
476
476
|
"- Package: `@beignet/provider-event-bus-redis`",
|
|
477
477
|
"- Peer dependency: `ioredis`",
|
|
478
|
-
"- The preset wires `createRedisEventBusProvider()` in `server/providers.ts` and defers `eventBus` in `infra/
|
|
478
|
+
"- The preset wires `createRedisEventBusProvider()` in `server/providers.ts` and defers `eventBus` in `infra/port-wiring.ts`.",
|
|
479
479
|
"- Set `REDIS_EVENT_BUS_URL` before booting the app.",
|
|
480
480
|
"- Redis Pub/Sub delivery is best-effort and cross-process, but not durable: use outbox/jobs for side effects that need retry, replay, or dead-letter handling.",
|
|
481
481
|
"- Use `ctx.ports.eventBus` in app code; `ctx.ports.redisEventBus` is available as an escape hatch.",
|
|
@@ -518,7 +518,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
518
518
|
"",
|
|
519
519
|
"- Package: `@beignet/provider-locks-redis`",
|
|
520
520
|
"- Peer dependency: `ioredis`",
|
|
521
|
-
"- The preset wires `createRedisLocksProvider()` in `server/providers.ts` and defers `locks` in `infra/
|
|
521
|
+
"- The preset wires `createRedisLocksProvider()` in `server/providers.ts` and defers `locks` in `infra/port-wiring.ts`.",
|
|
522
522
|
"- Set `REDIS_LOCKS_URL` before booting the app.",
|
|
523
523
|
"- Use `ctx.ports.locks` in app code; `ctx.ports.redisLocks` is available as an escape hatch.",
|
|
524
524
|
"",
|
|
@@ -579,7 +579,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
579
579
|
"",
|
|
580
580
|
"- Package: `@beignet/provider-storage-s3`",
|
|
581
581
|
"- Peer dependencies: `@aws-sdk/client-s3` and `@aws-sdk/s3-request-presigner`",
|
|
582
|
-
"- The preset wires `createS3StorageProvider()` in `server/providers.ts` and defers `storage` in `infra/
|
|
582
|
+
"- The preset wires `createS3StorageProvider()` in `server/providers.ts` and defers `storage` in `infra/port-wiring.ts`.",
|
|
583
583
|
"- Set `STORAGE_S3_BUCKET` before booting the app; set endpoint and credentials when using an S3-compatible host outside AWS defaults.",
|
|
584
584
|
"- Use `ctx.ports.storage` in app code; `ctx.ports.s3Storage` is available as an escape hatch.",
|
|
585
585
|
"",
|
|
@@ -625,7 +625,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
625
625
|
"",
|
|
626
626
|
"- Package: `@beignet/provider-storage-vercel-blob`",
|
|
627
627
|
"- Peer dependency: `@vercel/blob`",
|
|
628
|
-
"- The preset wires `createVercelBlobStorageProvider()` in `server/providers.ts` and defers `storage` in `infra/
|
|
628
|
+
"- The preset wires `createVercelBlobStorageProvider()` in `server/providers.ts` and defers `storage` in `infra/port-wiring.ts`.",
|
|
629
629
|
"- On Vercel, connect a Blob store to the project and `BLOB_READ_WRITE_TOKEN` is set automatically; set it manually elsewhere.",
|
|
630
630
|
"- The store is uniform-visibility (`BLOB_ACCESS`, default `private`): Vercel Blob does not report per-object access back, so mixed visibility needs a second store.",
|
|
631
631
|
"- Use `ctx.ports.storage` in app code; `ctx.ports.vercelBlob` is available as an escape hatch.",
|
|
@@ -673,7 +673,7 @@ const presets: Record<ProviderPresetName, ProviderPreset> = {
|
|
|
673
673
|
"",
|
|
674
674
|
"- Package: `@beignet/provider-rate-limit-upstash`",
|
|
675
675
|
"- Peer dependencies: `@upstash/ratelimit` and `@upstash/redis`",
|
|
676
|
-
"- The preset wires `createUpstashRateLimitProvider()` in `server/providers.ts` and defers `rateLimit` in `infra/
|
|
676
|
+
"- The preset wires `createUpstashRateLimitProvider()` in `server/providers.ts` and defers `rateLimit` in `infra/port-wiring.ts`.",
|
|
677
677
|
"- `.env.example` ships dev placeholders so the app boots; set real `UPSTASH_REDIS_REST_URL` and `UPSTASH_REDIS_REST_TOKEN` values before rate limiting requests.",
|
|
678
678
|
"- Use `ctx.ports.rateLimit` in app code; `ctx.ports.upstash` is available as an escape hatch.",
|
|
679
679
|
"",
|
|
@@ -778,9 +778,9 @@ export async function addProviderPreset(
|
|
|
778
778
|
),
|
|
779
779
|
},
|
|
780
780
|
{
|
|
781
|
-
path: config.paths.
|
|
781
|
+
path: config.paths.portWiring,
|
|
782
782
|
content: updateInfrastructurePortsFile(
|
|
783
|
-
await readRequiredAppFile(targetDir, config.paths.
|
|
783
|
+
await readRequiredAppFile(targetDir, config.paths.portWiring),
|
|
784
784
|
preset.appPort.key,
|
|
785
785
|
),
|
|
786
786
|
},
|
|
@@ -1056,13 +1056,17 @@ function ensureTypeProperty(
|
|
|
1056
1056
|
function ensureDeferredPort(source: string, portKey: string): string {
|
|
1057
1057
|
const match = /\bdeferred\s*:\s*\[/.exec(source);
|
|
1058
1058
|
if (!match) {
|
|
1059
|
-
throw new Error(
|
|
1059
|
+
throw new Error(
|
|
1060
|
+
"Could not find `deferred: [...]` in infra/port-wiring.ts.",
|
|
1061
|
+
);
|
|
1060
1062
|
}
|
|
1061
1063
|
|
|
1062
1064
|
const openBracket = source.indexOf("[", match.index);
|
|
1063
1065
|
const closeBracket = matchingDelimiterIndex(source, openBracket, "[", "]");
|
|
1064
1066
|
if (closeBracket === -1) {
|
|
1065
|
-
throw new Error(
|
|
1067
|
+
throw new Error(
|
|
1068
|
+
"Could not parse `deferred` array in infra/port-wiring.ts.",
|
|
1069
|
+
);
|
|
1066
1070
|
}
|
|
1067
1071
|
|
|
1068
1072
|
const arrayText = source.slice(openBracket, closeBracket + 1);
|
package/src/provider-audit.ts
CHANGED
|
@@ -149,7 +149,7 @@ export async function auditProviders(
|
|
|
149
149
|
? await readCachedSource(targetDir, config.paths.ports, sourceCache)
|
|
150
150
|
: "";
|
|
151
151
|
const infrastructurePath = directoryPath(
|
|
152
|
-
path.dirname(config.paths.
|
|
152
|
+
path.dirname(config.paths.portWiring),
|
|
153
153
|
);
|
|
154
154
|
const schemaDir = `${infrastructurePath}/db/schema`;
|
|
155
155
|
|
|
@@ -1015,6 +1015,14 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
1015
1015
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1016
1016
|
}
|
|
1017
1017
|
|
|
1018
|
+
function configuredProviderRequiredEnv(
|
|
1019
|
+
envVars: readonly string[],
|
|
1020
|
+
config: ResolvedBeignetConfig,
|
|
1021
|
+
): string[] {
|
|
1022
|
+
const ignored = new Set(config.providerAudit.ignoreRequiredEnv);
|
|
1023
|
+
return envVars.filter((envVar) => !ignored.has(envVar));
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1018
1026
|
function configuredProviderRequiredTables(
|
|
1019
1027
|
tableNames: readonly string[],
|
|
1020
1028
|
config: ResolvedBeignetConfig,
|
|
@@ -1140,13 +1148,18 @@ async function providerAuditEntryForRule(options: {
|
|
|
1140
1148
|
const requiredEnv =
|
|
1141
1149
|
options.rule.variants !== undefined
|
|
1142
1150
|
? aggregateVariantRequirement(variants, "requiredEnv")
|
|
1143
|
-
: await requirementAudit(
|
|
1144
|
-
|
|
1145
|
-
options.
|
|
1146
|
-
options.
|
|
1147
|
-
envVar,
|
|
1148
|
-
options.sourceCache,
|
|
1151
|
+
: await requirementAudit(
|
|
1152
|
+
configuredProviderRequiredEnv(
|
|
1153
|
+
options.rule.requiredEnv ?? [],
|
|
1154
|
+
options.config,
|
|
1149
1155
|
),
|
|
1156
|
+
(envVar) =>
|
|
1157
|
+
providerRequiredEnvExists(
|
|
1158
|
+
options.targetDir,
|
|
1159
|
+
options.configFiles,
|
|
1160
|
+
envVar,
|
|
1161
|
+
options.sourceCache,
|
|
1162
|
+
),
|
|
1150
1163
|
);
|
|
1151
1164
|
|
|
1152
1165
|
const requiredTables =
|
|
@@ -1222,12 +1235,16 @@ async function providerVariantAudit(options: {
|
|
|
1222
1235
|
variant: ProviderDoctorVariantRule;
|
|
1223
1236
|
registered: boolean;
|
|
1224
1237
|
}): Promise<ProviderVariantAudit> {
|
|
1238
|
+
const requiredEnv = configuredProviderRequiredEnv(
|
|
1239
|
+
options.variant.requiredEnv,
|
|
1240
|
+
options.config,
|
|
1241
|
+
);
|
|
1225
1242
|
if (!options.registered) {
|
|
1226
1243
|
return {
|
|
1227
1244
|
name: options.variant.name,
|
|
1228
1245
|
displayName: options.variant.displayName,
|
|
1229
1246
|
registration: "inactive",
|
|
1230
|
-
requiredEnv: inactiveRequirement(
|
|
1247
|
+
requiredEnv: inactiveRequirement(requiredEnv),
|
|
1231
1248
|
requiredTables: inactiveRequirement(options.variant.requiredTables),
|
|
1232
1249
|
};
|
|
1233
1250
|
}
|
|
@@ -1240,7 +1257,7 @@ async function providerVariantAudit(options: {
|
|
|
1240
1257
|
options.variant.registrationSeverity,
|
|
1241
1258
|
options.providerEntries,
|
|
1242
1259
|
),
|
|
1243
|
-
requiredEnv: await requirementAudit(
|
|
1260
|
+
requiredEnv: await requirementAudit(requiredEnv, (envVar) =>
|
|
1244
1261
|
providerRequiredEnvExists(
|
|
1245
1262
|
options.targetDir,
|
|
1246
1263
|
options.configFiles,
|
|
@@ -1662,13 +1679,13 @@ function isInfraOrServerFile(
|
|
|
1662
1679
|
): boolean {
|
|
1663
1680
|
const serverDir = directoryPath(path.dirname(config.paths.server));
|
|
1664
1681
|
const infrastructureDir = directoryPath(
|
|
1665
|
-
path.dirname(config.paths.
|
|
1682
|
+
path.dirname(config.paths.portWiring),
|
|
1666
1683
|
);
|
|
1667
1684
|
|
|
1668
1685
|
return (
|
|
1669
1686
|
file === config.paths.server ||
|
|
1670
1687
|
file.startsWith(`${serverDir}/`) ||
|
|
1671
|
-
file === config.paths.
|
|
1688
|
+
file === config.paths.portWiring ||
|
|
1672
1689
|
file.startsWith(`${infrastructureDir}/`)
|
|
1673
1690
|
);
|
|
1674
1691
|
}
|
package/src/schedule.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile, stat } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import type { ErrorReporterPort } from "@beignet/core/error-reporting";
|
|
3
4
|
import type { ProviderInstrumentationPort } from "@beignet/core/providers";
|
|
4
5
|
import type {
|
|
5
6
|
ScheduleDef,
|
|
@@ -8,6 +9,10 @@ import type {
|
|
|
8
9
|
} from "@beignet/core/schedules";
|
|
9
10
|
import { createJiti } from "jiti";
|
|
10
11
|
import { loadBeignetConfig, normalizePath } from "./config.js";
|
|
12
|
+
import {
|
|
13
|
+
flushOperationalErrorReporter,
|
|
14
|
+
reportOperationalFailure,
|
|
15
|
+
} from "./operational-error-reporting.js";
|
|
11
16
|
|
|
12
17
|
/**
|
|
13
18
|
* Options for running one app-owned schedule.
|
|
@@ -56,6 +61,7 @@ type ScheduleContext = {
|
|
|
56
61
|
logger?: ScheduleLogger;
|
|
57
62
|
devtools?: ProviderInstrumentationPort;
|
|
58
63
|
instrumentation?: ProviderInstrumentationPort;
|
|
64
|
+
errorReporter?: ErrorReporterPort;
|
|
59
65
|
};
|
|
60
66
|
};
|
|
61
67
|
|
|
@@ -131,7 +137,31 @@ export async function runAppSchedule(
|
|
|
131
137
|
},
|
|
132
138
|
});
|
|
133
139
|
|
|
134
|
-
|
|
140
|
+
try {
|
|
141
|
+
await runner.run(schedule, runOptions);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
await reportOperationalFailure({
|
|
144
|
+
ctx: scheduleContext,
|
|
145
|
+
error,
|
|
146
|
+
reportOptions: {
|
|
147
|
+
level: "error",
|
|
148
|
+
mechanism: "beignet.schedule.cli",
|
|
149
|
+
handled: false,
|
|
150
|
+
tags: {
|
|
151
|
+
"beignet.kind": "schedule",
|
|
152
|
+
"beignet.schedule": schedule.name,
|
|
153
|
+
},
|
|
154
|
+
contexts: {
|
|
155
|
+
schedule: {
|
|
156
|
+
name: schedule.name,
|
|
157
|
+
source: runOptions.source ?? "beignet-cli",
|
|
158
|
+
attempt: runOptions.attempt ?? null,
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
135
165
|
|
|
136
166
|
return {
|
|
137
167
|
schemaVersion: 1,
|
|
@@ -148,6 +178,7 @@ export async function runAppSchedule(
|
|
|
148
178
|
durationMs: Math.round(performance.now() - startedAt),
|
|
149
179
|
};
|
|
150
180
|
} finally {
|
|
181
|
+
await flushOperationalErrorReporter(scheduleContext);
|
|
151
182
|
await scheduleModule.stopScheduleContext?.(ctx, contextArgs);
|
|
152
183
|
}
|
|
153
184
|
}
|
package/src/task.ts
CHANGED
|
@@ -3,6 +3,11 @@ import path from "node:path";
|
|
|
3
3
|
import type { TaskDef, TaskRunContextArgs } from "@beignet/core/tasks";
|
|
4
4
|
import { createJiti } from "jiti";
|
|
5
5
|
import { loadBeignetConfig, normalizePath } from "./config.js";
|
|
6
|
+
import {
|
|
7
|
+
flushOperationalErrorReporter,
|
|
8
|
+
type OperationalErrorReportingContext,
|
|
9
|
+
reportOperationalFailure,
|
|
10
|
+
} from "./operational-error-reporting.js";
|
|
6
11
|
|
|
7
12
|
/**
|
|
8
13
|
* Options for running an app-owned operational task.
|
|
@@ -63,10 +68,35 @@ export async function runAppTask(
|
|
|
63
68
|
: {};
|
|
64
69
|
|
|
65
70
|
try {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
let output: unknown;
|
|
72
|
+
try {
|
|
73
|
+
output = await runTask(task, {
|
|
74
|
+
input: parsedInput,
|
|
75
|
+
ctx,
|
|
76
|
+
});
|
|
77
|
+
} catch (error) {
|
|
78
|
+
await reportOperationalFailure({
|
|
79
|
+
ctx: toOperationalContext(ctx),
|
|
80
|
+
error,
|
|
81
|
+
reportOptions: {
|
|
82
|
+
level: "error",
|
|
83
|
+
mechanism: "beignet.task.cli",
|
|
84
|
+
handled: false,
|
|
85
|
+
tags: {
|
|
86
|
+
"beignet.kind": "task",
|
|
87
|
+
"beignet.task": task.name,
|
|
88
|
+
},
|
|
89
|
+
contexts: {
|
|
90
|
+
task: {
|
|
91
|
+
name: task.name,
|
|
92
|
+
tenant: options.tenant ?? null,
|
|
93
|
+
source: "beignet-cli",
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
70
100
|
|
|
71
101
|
return {
|
|
72
102
|
schemaVersion: 1,
|
|
@@ -79,10 +109,17 @@ export async function runAppTask(
|
|
|
79
109
|
durationMs: Math.round(performance.now() - startedAt),
|
|
80
110
|
};
|
|
81
111
|
} finally {
|
|
112
|
+
await flushOperationalErrorReporter(toOperationalContext(ctx));
|
|
82
113
|
await taskModule.stopTaskContext?.(ctx, contextArgs);
|
|
83
114
|
}
|
|
84
115
|
}
|
|
85
116
|
|
|
117
|
+
function toOperationalContext(ctx: unknown): OperationalErrorReportingContext {
|
|
118
|
+
return typeof ctx === "object" && ctx !== null
|
|
119
|
+
? (ctx as OperationalErrorReportingContext)
|
|
120
|
+
: {};
|
|
121
|
+
}
|
|
122
|
+
|
|
86
123
|
function parseTaskInputFlag(input: RunAppTaskOptions["input"]): unknown {
|
|
87
124
|
if (input === undefined) return {};
|
|
88
125
|
if (typeof input !== "string") return input;
|
package/src/templates/base.ts
CHANGED
|
@@ -314,7 +314,7 @@ Use \`${cli} make feature projects --recipe full-slice\` when you want a richer
|
|
|
314
314
|
- \`features/shared/errors.ts\` keeps application errors and route-owned error schemas together.
|
|
315
315
|
- \`ports/\` defines app-owned dependencies.
|
|
316
316
|
- \`infra/\` implements ports for the selected runtime.
|
|
317
|
-
- \`infra/db/schema/\` contains the Drizzle schema, \`drizzle/\` contains the checked-in migrations, and \`infra/todos/\` contains the durable todo repository adapter.
|
|
317
|
+
- \`infra/db/client.ts\` owns the process-wide database client shared by Better Auth and the Beignet provider; the public server \`stop()\` closes it after provider shutdown. \`infra/db/schema/\` contains the Drizzle schema, \`drizzle/\` contains the checked-in migrations, and \`infra/todos/\` contains the durable todo repository adapter.
|
|
318
318
|
- \`server/routes.ts\` keeps the central route registry and OpenAPI contract list.
|
|
319
319
|
- \`server/context.ts\` declares the context blueprint shared by the server and route tests.
|
|
320
320
|
- \`server/providers.ts\` wires devtools, Better Auth, pino, ${
|
|
@@ -342,6 +342,7 @@ ${optionalAuthRedirects}
|
|
|
342
342
|
cli +
|
|
343
343
|
" db migrate` against it before starting the app."
|
|
344
344
|
}
|
|
345
|
+
- Size the process-wide database client for the deployment. Postgres and MySQL use the generated pool-limit environment variable; count web, worker, preview, and task processes against the database connection limit, and keep writable SQLite files on one host.
|
|
345
346
|
- Run \`${cli} db generate\` and \`${cli} db migrate\` after changing the Drizzle schema.
|
|
346
347
|
- Run \`${cli} db reset\` to rebuild a local ${
|
|
347
348
|
ctx.database === "sqlite"
|
|
@@ -438,11 +439,15 @@ export function envExample(ctx: TemplateContext): string {
|
|
|
438
439
|
"# Local Postgres matching the docker one-liner below; point at your own server in production.",
|
|
439
440
|
`# ${databaseStartCommand("postgres", ctx.name)}`,
|
|
440
441
|
`POSTGRES_DB_URL=${databaseLocalUrl("postgres", ctx.name)}`,
|
|
442
|
+
"# Maximum connections opened by each running app process.",
|
|
443
|
+
"POSTGRES_DB_POOL_MAX=5",
|
|
441
444
|
]
|
|
442
445
|
: [
|
|
443
446
|
"# Local MySQL matching the docker one-liner below; point at your own server in production.",
|
|
444
447
|
`# ${databaseStartCommand("mysql", ctx.name)}`,
|
|
445
448
|
`MYSQL_DB_URL=${databaseLocalUrl("mysql", ctx.name)}`,
|
|
449
|
+
"# Maximum connections opened by each running app process.",
|
|
450
|
+
"MYSQL_DB_POOL_MAX=5",
|
|
446
451
|
];
|
|
447
452
|
|
|
448
453
|
const lines = [
|
|
@@ -36,6 +36,22 @@ export function mysqlDbFiles(ctx: TemplateContext): DbTemplateFiles {
|
|
|
36
36
|
url: process.env.MYSQL_DB_URL ?? "${databaseLocalUrl("mysql", ctx.name)}",
|
|
37
37
|
},
|
|
38
38
|
};
|
|
39
|
+
`,
|
|
40
|
+
dbClient: `import "@beignet/core/server-only";
|
|
41
|
+
import mysql from "mysql2/promise";
|
|
42
|
+
import { env } from "@/lib/env";
|
|
43
|
+
|
|
44
|
+
export const databaseClient = mysql.createPool({
|
|
45
|
+
uri: env.MYSQL_DB_URL,
|
|
46
|
+
connectionLimit: env.MYSQL_DB_POOL_MAX,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
let closePromise: Promise<void> | undefined;
|
|
50
|
+
|
|
51
|
+
export function closeDatabaseClient(): Promise<void> {
|
|
52
|
+
closePromise ??= Promise.resolve().then(() => databaseClient.end());
|
|
53
|
+
return closePromise;
|
|
54
|
+
}
|
|
39
55
|
`,
|
|
40
56
|
dbSchema: `import {
|
|
41
57
|
boolean,
|
|
@@ -33,6 +33,22 @@ export function postgresDbFiles(ctx: TemplateContext): DbTemplateFiles {
|
|
|
33
33
|
url: process.env.POSTGRES_DB_URL ?? "${databaseLocalUrl("postgres", ctx.name)}",
|
|
34
34
|
},
|
|
35
35
|
};
|
|
36
|
+
`,
|
|
37
|
+
dbClient: `import "@beignet/core/server-only";
|
|
38
|
+
import pg from "pg";
|
|
39
|
+
import { env } from "@/lib/env";
|
|
40
|
+
|
|
41
|
+
export const databaseClient = new pg.Pool({
|
|
42
|
+
connectionString: env.POSTGRES_DB_URL,
|
|
43
|
+
max: env.POSTGRES_DB_POOL_MAX,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
let closePromise: Promise<void> | undefined;
|
|
47
|
+
|
|
48
|
+
export function closeDatabaseClient(): Promise<void> {
|
|
49
|
+
closePromise ??= Promise.resolve().then(() => databaseClient.end());
|
|
50
|
+
return closePromise;
|
|
51
|
+
}
|
|
36
52
|
`,
|
|
37
53
|
dbSchema: `import { boolean, index, pgTable, text, timestamp } from "drizzle-orm/pg-core";
|
|
38
54
|
|
|
@@ -24,6 +24,22 @@ const files = {
|
|
|
24
24
|
authToken: process.env.SQLITE_DB_AUTH_TOKEN,
|
|
25
25
|
},
|
|
26
26
|
};
|
|
27
|
+
`,
|
|
28
|
+
dbClient: `import "@beignet/core/server-only";
|
|
29
|
+
import { createClient } from "@libsql/client";
|
|
30
|
+
import { env } from "@/lib/env";
|
|
31
|
+
|
|
32
|
+
export const databaseClient = createClient({
|
|
33
|
+
url: env.SQLITE_DB_URL,
|
|
34
|
+
authToken: env.SQLITE_DB_AUTH_TOKEN,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
let closePromise: Promise<void> | undefined;
|
|
38
|
+
|
|
39
|
+
export function closeDatabaseClient(): Promise<void> {
|
|
40
|
+
closePromise ??= Promise.resolve().then(() => databaseClient.close());
|
|
41
|
+
return closePromise;
|
|
42
|
+
}
|
|
27
43
|
`,
|
|
28
44
|
dbSchema: `import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
29
45
|
|
package/src/templates/index.ts
CHANGED
|
@@ -19,8 +19,8 @@ import { dbFiles } from "./db/index.js";
|
|
|
19
19
|
import {
|
|
20
20
|
betterAuth,
|
|
21
21
|
env,
|
|
22
|
-
infrastructurePorts,
|
|
23
22
|
ports,
|
|
23
|
+
portWiring,
|
|
24
24
|
server,
|
|
25
25
|
serverFiles,
|
|
26
26
|
serverProviders,
|
|
@@ -109,7 +109,8 @@ export function getTemplateFiles(ctx: TemplateContext): TemplateFile[] {
|
|
|
109
109
|
},
|
|
110
110
|
{ path: "ports/index.ts", content: ports(ctx) },
|
|
111
111
|
{ path: "ports/auth.ts", content: serverFiles.authPort },
|
|
112
|
-
{ path: "infra/
|
|
112
|
+
{ path: "infra/port-wiring.ts", content: portWiring(ctx) },
|
|
113
|
+
{ path: "infra/db/client.ts", content: db.dbClient },
|
|
113
114
|
{ path: "infra/db/database-ready.ts", content: db.databaseReady },
|
|
114
115
|
{ path: "infra/db/provider.ts", content: db.dbProvider },
|
|
115
116
|
{ path: "infra/db/repositories.ts", content: db.dbRepositories },
|
|
@@ -137,7 +138,7 @@ export function getTemplateFiles(ctx: TemplateContext): TemplateFile[] {
|
|
|
137
138
|
...testSupportTemplateFiles,
|
|
138
139
|
{ path: "lib/use-case.ts", content: serverFiles.useCaseBuilder },
|
|
139
140
|
{ path: "lib/routes.ts", content: serverFiles.routesBuilder },
|
|
140
|
-
{ path: "server/index.ts", content: server(
|
|
141
|
+
{ path: "server/index.ts", content: server() },
|
|
141
142
|
{ path: "server/context.ts", content: serverFiles.serverContext },
|
|
142
143
|
{ path: "server/routes.ts", content: serverFiles.serverRoutes },
|
|
143
144
|
{ path: "server/providers.ts", content: serverProviders(ctx) },
|