@beignet/cli 0.0.37 → 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 +17 -0
- package/README.md +18 -5
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/inspect.js +15 -15
- 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.js +3 -3
- 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/index.js +2 -2
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/server.d.ts +1 -1
- package/dist/templates/server.d.ts.map +1 -1
- package/dist/templates/server.js +5 -5
- package/dist/templates/server.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 +2 -2
- package/src/inspect.ts +15 -17
- 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 +3 -3
- package/src/schedule.ts +32 -1
- package/src/task.ts +41 -4
- package/src/templates/index.ts +2 -2
- package/src/templates/server.ts +5 -5
- package/src/templates/testing.ts +1 -1
- package/src/templates/todos.ts +6 -6
package/src/make/tenancy.ts
CHANGED
|
@@ -330,7 +330,7 @@ export async function updateTenancyWiring(
|
|
|
330
330
|
if (
|
|
331
331
|
await updateWorkspaceInfrastructurePorts(targetDir, config, editOptions)
|
|
332
332
|
) {
|
|
333
|
-
updated.add(config.paths.
|
|
333
|
+
updated.add(config.paths.portWiring);
|
|
334
334
|
}
|
|
335
335
|
if (
|
|
336
336
|
await updateDrizzleSchemaIndexExports(
|
|
@@ -524,7 +524,7 @@ export async function updateWorkspaceInfrastructurePorts(
|
|
|
524
524
|
config: ResolvedBeignetConfig,
|
|
525
525
|
options: { dryRun: boolean },
|
|
526
526
|
): Promise<boolean> {
|
|
527
|
-
const filePath = path.join(targetDir, config.paths.
|
|
527
|
+
const filePath = path.join(targetDir, config.paths.portWiring);
|
|
528
528
|
const original = await readFile(filePath, "utf8");
|
|
529
529
|
let next = original;
|
|
530
530
|
|
|
@@ -532,21 +532,21 @@ export async function updateWorkspaceInfrastructurePorts(
|
|
|
532
532
|
const appended = appendDeferredPortKey(next, portKey);
|
|
533
533
|
if (appended === next && !new RegExp(`["']${portKey}["']`).test(next)) {
|
|
534
534
|
throw new Error(
|
|
535
|
-
`Could not find the deferred port list in ${config.paths.
|
|
535
|
+
`Could not find the deferred port list in ${config.paths.portWiring}. Add "${portKey}" manually, or restore the generated port wiring file before running make tenancy.`,
|
|
536
536
|
);
|
|
537
537
|
}
|
|
538
538
|
next = appended;
|
|
539
539
|
}
|
|
540
540
|
|
|
541
541
|
const policyModule = relativeModule(
|
|
542
|
-
config.paths.
|
|
542
|
+
config.paths.portWiring,
|
|
543
543
|
path.join(config.paths.features, "workspaces/policy.ts"),
|
|
544
544
|
);
|
|
545
545
|
next = addNamedImport(next, "workspacePolicy", policyModule);
|
|
546
546
|
next = appendCreateGatePolicy(
|
|
547
547
|
next,
|
|
548
548
|
"workspacePolicy",
|
|
549
|
-
`Could not find createGate({ policies: [...] }) in ${config.paths.
|
|
549
|
+
`Could not find createGate({ policies: [...] }) in ${config.paths.portWiring}. Add workspacePolicy to the gate policies manually, or restore the generated port wiring file before running make tenancy.`,
|
|
550
550
|
);
|
|
551
551
|
|
|
552
552
|
if (next === original) return false;
|
|
@@ -2274,7 +2274,7 @@ import type {
|
|
|
2274
2274
|
\tWorkspaceMemberWithUser,
|
|
2275
2275
|
\tWorkspaceRepository,
|
|
2276
2276
|
} from "@/features/workspaces/ports";
|
|
2277
|
-
import {
|
|
2277
|
+
import { initialPorts } from "@/infra/port-wiring";
|
|
2278
2278
|
import type { AppTransactionPorts } from "@/ports";
|
|
2279
2279
|
|
|
2280
2280
|
export type MemoryWorkspacePorts = {
|
|
@@ -2468,9 +2468,9 @@ export type WorkspaceTestFixture = TestPortsFixture<
|
|
|
2468
2468
|
export function createWorkspaceFixture(): WorkspaceTestFixture {
|
|
2469
2469
|
\tconst repositories = createMemoryWorkspacePorts();
|
|
2470
2470
|
\tconst fixture = createTestPorts<AppContext["ports"], AppTransactionPorts>({
|
|
2471
|
-
\t\tbase:
|
|
2471
|
+
\t\tbase: initialPorts,
|
|
2472
2472
|
\t\toverrides: {
|
|
2473
|
-
\t\t\tgate:
|
|
2473
|
+
\t\t\tgate: initialPorts.gate,
|
|
2474
2474
|
\t\t\t...repositories,
|
|
2475
2475
|
\t\t},
|
|
2476
2476
|
\t});
|
package/src/make.ts
CHANGED
|
@@ -911,7 +911,7 @@ export async function makePort(
|
|
|
911
911
|
dryRun: Boolean(options.dryRun),
|
|
912
912
|
})
|
|
913
913
|
) {
|
|
914
|
-
updatedFiles.push(config.paths.
|
|
914
|
+
updatedFiles.push(config.paths.portWiring);
|
|
915
915
|
}
|
|
916
916
|
|
|
917
917
|
return {
|
|
@@ -957,7 +957,7 @@ export async function makeAdapter(
|
|
|
957
957
|
dryRun: Boolean(options.dryRun),
|
|
958
958
|
})
|
|
959
959
|
) {
|
|
960
|
-
updatedFiles.push(config.paths.
|
|
960
|
+
updatedFiles.push(config.paths.portWiring);
|
|
961
961
|
}
|
|
962
962
|
|
|
963
963
|
return {
|
|
@@ -1034,6 +1034,9 @@ export async function makeEvent(
|
|
|
1034
1034
|
const outboxDrainPlan = await planOutboxDrainWiring(targetDir, config, {
|
|
1035
1035
|
force: Boolean(options.force),
|
|
1036
1036
|
});
|
|
1037
|
+
await updateOutboxRegistry(targetDir, names, "events", config, {
|
|
1038
|
+
dryRun: true,
|
|
1039
|
+
});
|
|
1037
1040
|
|
|
1038
1041
|
const result = await makeFeatureArtifact({
|
|
1039
1042
|
targetDir,
|
|
@@ -1078,6 +1081,9 @@ export async function makeJob(options: MakeJobOptions): Promise<MakeJobResult> {
|
|
|
1078
1081
|
const outboxDrainPlan = await planOutboxDrainWiring(targetDir, config, {
|
|
1079
1082
|
force: Boolean(options.force),
|
|
1080
1083
|
});
|
|
1084
|
+
await updateOutboxRegistry(targetDir, names, "jobs", config, {
|
|
1085
|
+
dryRun: true,
|
|
1086
|
+
});
|
|
1081
1087
|
|
|
1082
1088
|
const result = await makeFeatureArtifact({
|
|
1083
1089
|
targetDir,
|
|
@@ -1697,7 +1703,7 @@ async function updateOutboxPortWiring(
|
|
|
1697
1703
|
changes.updatedFiles.push(config.paths.ports);
|
|
1698
1704
|
}
|
|
1699
1705
|
if (await updateOutboxInfrastructurePorts(targetDir, config, options)) {
|
|
1700
|
-
changes.updatedFiles.push(config.paths.
|
|
1706
|
+
changes.updatedFiles.push(config.paths.portWiring);
|
|
1701
1707
|
}
|
|
1702
1708
|
if (await updateOutboxDatabaseProvider(targetDir, config, options)) {
|
|
1703
1709
|
changes.updatedFiles.push(
|
|
@@ -1805,7 +1811,7 @@ async function updateOutboxInfrastructurePorts(
|
|
|
1805
1811
|
config: ResolvedBeignetConfig,
|
|
1806
1812
|
options: { dryRun: boolean },
|
|
1807
1813
|
): Promise<boolean> {
|
|
1808
|
-
const filePath = path.join(targetDir, config.paths.
|
|
1814
|
+
const filePath = path.join(targetDir, config.paths.portWiring);
|
|
1809
1815
|
const original = await readFile(filePath, "utf8");
|
|
1810
1816
|
let next = appendDeferredPortKey(original, "outbox");
|
|
1811
1817
|
next = appendDeferredPortKey(next, "outboxAdmin");
|
|
@@ -2166,7 +2172,7 @@ export async function makeOutbox(
|
|
|
2166
2172
|
...cronSecretPlan.files.map((file) => file.path),
|
|
2167
2173
|
...cronSecretPlan.skippedFiles,
|
|
2168
2174
|
config.paths.ports,
|
|
2169
|
-
config.paths.
|
|
2175
|
+
config.paths.portWiring,
|
|
2170
2176
|
path.join(infrastructureDir(config), "db/provider.ts"),
|
|
2171
2177
|
drizzleRepositoriesPath(config),
|
|
2172
2178
|
outboxDrizzleSchemaFilePath(config),
|
|
@@ -2361,19 +2367,19 @@ async function updateResourceWiring(
|
|
|
2361
2367
|
persistence === "memory" &&
|
|
2362
2368
|
(await updateInfrastructurePorts(targetDir, names, config, options))
|
|
2363
2369
|
) {
|
|
2364
|
-
updated.add(config.paths.
|
|
2370
|
+
updated.add(config.paths.portWiring);
|
|
2365
2371
|
}
|
|
2366
2372
|
if (
|
|
2367
2373
|
generationOptions.auth &&
|
|
2368
2374
|
(await updateInfrastructureGatePolicies(targetDir, names, config, options))
|
|
2369
2375
|
) {
|
|
2370
|
-
updated.add(config.paths.
|
|
2376
|
+
updated.add(config.paths.portWiring);
|
|
2371
2377
|
}
|
|
2372
2378
|
if (
|
|
2373
2379
|
persistence === "drizzle" &&
|
|
2374
2380
|
(await updateInfrastructureDeferredPorts(targetDir, names, config, options))
|
|
2375
2381
|
) {
|
|
2376
|
-
updated.add(config.paths.
|
|
2382
|
+
updated.add(config.paths.portWiring);
|
|
2377
2383
|
}
|
|
2378
2384
|
if (
|
|
2379
2385
|
persistence === "drizzle" &&
|
|
@@ -3381,7 +3387,13 @@ async function updateOutboxRegistry(
|
|
|
3381
3387
|
importLine,
|
|
3382
3388
|
);
|
|
3383
3389
|
|
|
3384
|
-
if (appended.kind === "missing"
|
|
3390
|
+
if (appended.kind === "missing") {
|
|
3391
|
+
throw new Error(
|
|
3392
|
+
`Could not find the ${kind} array in ${config.paths.outbox}. Register ${registryName} manually, or restore the generated outbox file before running this command.`,
|
|
3393
|
+
);
|
|
3394
|
+
}
|
|
3395
|
+
|
|
3396
|
+
if (appended.kind === "unchanged") {
|
|
3385
3397
|
return "skipped";
|
|
3386
3398
|
}
|
|
3387
3399
|
|
|
@@ -3988,6 +4000,7 @@ export const outboxMessages = mysqlTable(
|
|
|
3988
4000
|
\t\tkind: varchar("kind", { length: 32 }).notNull(),
|
|
3989
4001
|
\t\tname: varchar("name", { length: 255 }).notNull(),
|
|
3990
4002
|
\t\tpayloadJson: longtext("payload_json").notNull(),
|
|
4003
|
+
\t\ttraceContextJson: longtext("trace_context_json"),
|
|
3991
4004
|
\t\tstatus: varchar("status", { length: 32 }).notNull(),
|
|
3992
4005
|
\t\tattempts: int("attempts").notNull().default(0),
|
|
3993
4006
|
\t\tmaxAttempts: int("max_attempts").notNull().default(3),
|
|
@@ -4023,6 +4036,7 @@ export const outboxMessages = ${dialect.tableFunction}(
|
|
|
4023
4036
|
\t\tkind: text("kind").notNull(),
|
|
4024
4037
|
\t\tname: text("name").notNull(),
|
|
4025
4038
|
\t\tpayloadJson: text("payload_json").notNull(),
|
|
4039
|
+
\t\ttraceContextJson: text("trace_context_json"),
|
|
4026
4040
|
\t\tstatus: text("status").notNull(),
|
|
4027
4041
|
\t\tattempts: integer("attempts").notNull().default(0),
|
|
4028
4042
|
\t\tmaxAttempts: integer("max_attempts").notNull().default(3),
|
|
@@ -4503,15 +4517,15 @@ import { createInMemoryDevtools } from "@beignet/devtools";
|
|
|
4503
4517
|
import { createTestContextFactory, createTestPorts } from "@beignet/core/testing";
|
|
4504
4518
|
import { createTestAnonymousActor } from "@beignet/core/testing";
|
|
4505
4519
|
import type { AppContext } from "${aliasModule(config.paths.appContext)}";
|
|
4506
|
-
import {
|
|
4520
|
+
import { initialPorts } from "${aliasModule(config.paths.portWiring)}";
|
|
4507
4521
|
import { ${names.exportName} } from "${relativeModule(filePath, useCaseFilePath(names, config))}";
|
|
4508
4522
|
|
|
4509
4523
|
describe("${names.exportName}", () => {
|
|
4510
4524
|
it("runs ${names.action.camel}", async () => {
|
|
4511
4525
|
const testFixture = createTestPorts<AppContext["ports"]>({
|
|
4512
|
-
base:
|
|
4526
|
+
base: initialPorts,
|
|
4513
4527
|
overrides: {
|
|
4514
|
-
gate:
|
|
4528
|
+
gate: initialPorts.gate,
|
|
4515
4529
|
devtools: createInMemoryDevtools(),
|
|
4516
4530
|
},
|
|
4517
4531
|
});
|
|
@@ -5276,7 +5290,7 @@ function testFile(
|
|
|
5276
5290
|
options: ResourceGenerationOptions,
|
|
5277
5291
|
): string {
|
|
5278
5292
|
const repositoryPath = path.join(
|
|
5279
|
-
path.dirname(config.paths.
|
|
5293
|
+
path.dirname(config.paths.portWiring),
|
|
5280
5294
|
names.pluralKebab,
|
|
5281
5295
|
`in-memory-${names.singularKebab}-repository.ts`,
|
|
5282
5296
|
);
|
|
@@ -5294,7 +5308,7 @@ import { createInMemoryDevtools } from "@beignet/devtools";
|
|
|
5294
5308
|
import { createTestContextFactory, createTestPorts } from "@beignet/core/testing";
|
|
5295
5309
|
import { ${options.auth ? "createTestUserActor" : "createTestAnonymousActor"}${options.tenant ? ", createTestTenant" : ""} } from "@beignet/core/testing";
|
|
5296
5310
|
import type { AppContext } from "${aliasModule(config.paths.appContext)}";
|
|
5297
|
-
import {
|
|
5311
|
+
import { initialPorts } from "${aliasModule(config.paths.portWiring)}";
|
|
5298
5312
|
import { appContext } from "${aliasModule(serverContextPath)}";
|
|
5299
5313
|
import {
|
|
5300
5314
|
create${names.singularPascal},
|
|
@@ -5334,7 +5348,7 @@ ${options.tenant ? `\t\t\t\ttenantId: "tenant_test",\n` : ""} name: "Gamma ${
|
|
|
5334
5348
|
${options.tenant ? `\t\t\t{\n\t\t\t\tid: "00000000-0000-4000-8000-000000000104",\n\t\t\t\ttenantId: "tenant_other",\n\t\t\t\tname: "Other Tenant ${names.singularPascal}",\n\t\t\t\tversion: 1,\n\t\t\t\tcreatedAt: "2024-01-04T00:00:00.000Z",\n\t\t\t\tupdatedAt: "2024-01-04T00:00:00.000Z",\n\t\t\t},\n` : ""} ];
|
|
5335
5349
|
const ${names.pluralCamel} = createInMemory${names.singularPascal}Repository(seed${names.pluralPascal});
|
|
5336
5350
|
const testFixture = createTestPorts<AppContext["ports"]>({
|
|
5337
|
-
base:
|
|
5351
|
+
base: initialPorts,
|
|
5338
5352
|
overrides: {
|
|
5339
5353
|
${names.pluralCamel},
|
|
5340
5354
|
${
|
|
@@ -5345,7 +5359,7 @@ ${
|
|
|
5345
5359
|
${options.tenant ? `\t\t\t\t\t\tsession: { tenantId: "tenant_test" },\n` : ""}
|
|
5346
5360
|
}),
|
|
5347
5361
|
},
|
|
5348
|
-
${options.auth ? `\t\t\t\tgate:
|
|
5362
|
+
${options.auth ? `\t\t\t\tgate: initialPorts.gate,\n` : ""}
|
|
5349
5363
|
`
|
|
5350
5364
|
: `\t\t\t\tauth: {
|
|
5351
5365
|
getSession: async () => null,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ErrorReporterPort,
|
|
3
|
+
ErrorReportOptions,
|
|
4
|
+
} from "@beignet/core/error-reporting";
|
|
5
|
+
|
|
6
|
+
export type OperationalErrorReportingContext = {
|
|
7
|
+
requestId?: string;
|
|
8
|
+
traceId?: string;
|
|
9
|
+
ports?: {
|
|
10
|
+
errorReporter?: ErrorReporterPort;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export async function reportOperationalFailure(args: {
|
|
15
|
+
ctx: OperationalErrorReportingContext;
|
|
16
|
+
error: unknown;
|
|
17
|
+
reportOptions: ErrorReportOptions;
|
|
18
|
+
}): Promise<void> {
|
|
19
|
+
const reporter = args.ctx.ports?.errorReporter;
|
|
20
|
+
if (!reporter) return;
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const { tryReportException } = await import(
|
|
24
|
+
"@beignet/core/error-reporting"
|
|
25
|
+
);
|
|
26
|
+
await tryReportException({
|
|
27
|
+
reporter,
|
|
28
|
+
error: args.error,
|
|
29
|
+
reportOptions: {
|
|
30
|
+
requestId: args.ctx.requestId,
|
|
31
|
+
traceId: args.ctx.traceId,
|
|
32
|
+
...args.reportOptions,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
} catch {
|
|
36
|
+
// Reporting must not replace the operational command failure.
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function flushOperationalErrorReporter(
|
|
41
|
+
ctx: OperationalErrorReportingContext,
|
|
42
|
+
timeoutMs = 1000,
|
|
43
|
+
): Promise<void> {
|
|
44
|
+
const reporter = ctx.ports?.errorReporter;
|
|
45
|
+
if (!reporter) return;
|
|
46
|
+
|
|
47
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
48
|
+
try {
|
|
49
|
+
await Promise.race([
|
|
50
|
+
reporter.flush({ timeoutMs }),
|
|
51
|
+
new Promise<boolean>((resolve) => {
|
|
52
|
+
timeout = setTimeout(() => resolve(false), timeoutMs);
|
|
53
|
+
}),
|
|
54
|
+
]);
|
|
55
|
+
} catch {
|
|
56
|
+
// Reporting shutdown must not replace the operational command result.
|
|
57
|
+
} finally {
|
|
58
|
+
if (timeout !== undefined) clearTimeout(timeout);
|
|
59
|
+
}
|
|
60
|
+
}
|
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
|
|
|
@@ -1679,13 +1679,13 @@ function isInfraOrServerFile(
|
|
|
1679
1679
|
): boolean {
|
|
1680
1680
|
const serverDir = directoryPath(path.dirname(config.paths.server));
|
|
1681
1681
|
const infrastructureDir = directoryPath(
|
|
1682
|
-
path.dirname(config.paths.
|
|
1682
|
+
path.dirname(config.paths.portWiring),
|
|
1683
1683
|
);
|
|
1684
1684
|
|
|
1685
1685
|
return (
|
|
1686
1686
|
file === config.paths.server ||
|
|
1687
1687
|
file.startsWith(`${serverDir}/`) ||
|
|
1688
|
-
file === config.paths.
|
|
1688
|
+
file === config.paths.portWiring ||
|
|
1689
1689
|
file.startsWith(`${infrastructureDir}/`)
|
|
1690
1690
|
);
|
|
1691
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
|
}
|