@beignet/cli 0.0.22 → 0.0.24

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.
Files changed (59) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +35 -5
  3. package/dist/choices.d.ts +8 -0
  4. package/dist/choices.d.ts.map +1 -1
  5. package/dist/choices.js +4 -0
  6. package/dist/choices.js.map +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +8 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/inspect.d.ts.map +1 -1
  11. package/dist/inspect.js +184 -0
  12. package/dist/inspect.js.map +1 -1
  13. package/dist/lib.d.ts +2 -2
  14. package/dist/lib.d.ts.map +1 -1
  15. package/dist/lib.js +1 -1
  16. package/dist/lib.js.map +1 -1
  17. package/dist/make.d.ts +4 -3
  18. package/dist/make.d.ts.map +1 -1
  19. package/dist/make.js +349 -45
  20. package/dist/make.js.map +1 -1
  21. package/dist/mcp.d.ts.map +1 -1
  22. package/dist/mcp.js +6 -2
  23. package/dist/mcp.js.map +1 -1
  24. package/dist/templates/agents.d.ts.map +1 -1
  25. package/dist/templates/agents.js +9 -5
  26. package/dist/templates/agents.js.map +1 -1
  27. package/dist/templates/base.d.ts.map +1 -1
  28. package/dist/templates/base.js +4 -2
  29. package/dist/templates/base.js.map +1 -1
  30. package/dist/templates/db/mysql.js +2 -2
  31. package/dist/templates/db/postgres.js +2 -2
  32. package/dist/templates/db/sqlite.js +2 -2
  33. package/dist/templates/index.d.ts.map +1 -1
  34. package/dist/templates/index.js +1 -0
  35. package/dist/templates/index.js.map +1 -1
  36. package/dist/templates/server.d.ts +1 -0
  37. package/dist/templates/server.d.ts.map +1 -1
  38. package/dist/templates/server.js +90 -20
  39. package/dist/templates/server.js.map +1 -1
  40. package/dist/templates/shell.d.ts.map +1 -1
  41. package/dist/templates/shell.js +60 -29
  42. package/dist/templates/shell.js.map +1 -1
  43. package/dist/templates/todos.js +5 -5
  44. package/package.json +2 -2
  45. package/src/choices.ts +10 -0
  46. package/src/index.ts +11 -0
  47. package/src/inspect.ts +319 -0
  48. package/src/lib.ts +2 -1
  49. package/src/make.ts +449 -53
  50. package/src/mcp.ts +7 -1
  51. package/src/templates/agents.ts +9 -5
  52. package/src/templates/base.ts +4 -2
  53. package/src/templates/db/mysql.ts +2 -2
  54. package/src/templates/db/postgres.ts +2 -2
  55. package/src/templates/db/sqlite.ts +2 -2
  56. package/src/templates/index.ts +1 -0
  57. package/src/templates/server.ts +90 -20
  58. package/src/templates/shell.ts +61 -29
  59. package/src/templates/todos.ts +5 -5
package/dist/make.js CHANGED
@@ -3,7 +3,7 @@ import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { clientIndexPath, directoryPath, loadBeignetConfig, normalizePath, resolveConfig, } from "./config.js";
5
5
  import { appendToArrayExpression, appendToNamedArray, appendToOutboxRegistryArray, arrayInitializerInfo, identifiersFromArrayExpression, insertAfterImports, matchingDelimiterIndex, } from "./registry-edits.js";
6
- export { makeFeatureAddonChoices } from "./choices.js";
6
+ export { makeFeatureAddonChoices, makeFeatureRecipeChoices, } from "./choices.js";
7
7
  const drizzleDialects = {
8
8
  sqlite: {
9
9
  subpath: "sqlite",
@@ -43,15 +43,23 @@ export async function makeResource(options) {
43
43
  return makeResourceSlice(options, "resource");
44
44
  }
45
45
  export async function makeFeature(options) {
46
- const addons = normalizeMakeFeatureAddons(options.with ?? []);
46
+ const addons = normalizeMakeFeatureAddons([
47
+ ...makeFeatureRecipeAddons(options.recipe),
48
+ ...(options.with ?? []),
49
+ ...(options.events ? ["events"] : []),
50
+ ]);
51
+ const featureOptions = {
52
+ ...options,
53
+ events: addons.includes("event"),
54
+ };
47
55
  if (addons.length === 0) {
48
- return makeFeatureResource(options);
56
+ return makeFeatureResource(featureOptions);
49
57
  }
50
58
  if (options.dryRun) {
51
- return makeFeatureSlice(options, addons);
59
+ return makeFeatureSlice(featureOptions, addons);
52
60
  }
53
- await makeFeatureSlice({ ...options, dryRun: true }, addons);
54
- return makeFeatureSlice(options, addons);
61
+ await makeFeatureSlice({ ...featureOptions, dryRun: true }, addons);
62
+ return makeFeatureSlice(featureOptions, addons);
55
63
  }
56
64
  export async function makePayments(options) {
57
65
  const targetDir = path.resolve(options.cwd ?? process.cwd());
@@ -191,6 +199,14 @@ async function makeResourceSlice(options, mode) {
191
199
  };
192
200
  }
193
201
  function resourceGenerationOptions(options, mode) {
202
+ if (mode === "feature") {
203
+ return {
204
+ auth: false,
205
+ tenant: false,
206
+ events: Boolean(options.events),
207
+ softDelete: false,
208
+ };
209
+ }
194
210
  if (mode !== "resource") {
195
211
  return { auth: false, tenant: false, events: false, softDelete: false };
196
212
  }
@@ -292,6 +308,24 @@ function normalizeMakeFeatureAddons(addons) {
292
308
  }
293
309
  return featureAddonOrder.filter((addon) => normalized.has(addon));
294
310
  }
311
+ function makeFeatureRecipeAddons(recipe) {
312
+ if (recipe === "full-slice") {
313
+ return [
314
+ "policy",
315
+ "factories",
316
+ "seeds",
317
+ "tasks",
318
+ "events",
319
+ "listeners",
320
+ "jobs",
321
+ "notifications",
322
+ "schedules",
323
+ "ui",
324
+ "uploads",
325
+ ];
326
+ }
327
+ return [];
328
+ }
295
329
  const featureAddonOrder = [
296
330
  "policy",
297
331
  "factory",
@@ -815,7 +849,10 @@ export async function makeListener(options) {
815
849
  if (!options.skipEventAssert) {
816
850
  await assertListenerEventExists(targetDir, names.event, config);
817
851
  }
818
- return makeFeatureArtifact({
852
+ await updateListenerProviderWiring(targetDir, names, config, {
853
+ dryRun: true,
854
+ });
855
+ const result = await makeFeatureArtifact({
819
856
  targetDir,
820
857
  config,
821
858
  force: Boolean(options.force),
@@ -830,6 +867,19 @@ export async function makeListener(options) {
830
867
  "@beignet/core": beignetDependencyVersion,
831
868
  },
832
869
  });
870
+ const providerResult = await updateListenerProviderWiring(targetDir, names, config, {
871
+ dryRun: Boolean(options.dryRun),
872
+ });
873
+ if (providerResult === "created")
874
+ result.createdFiles.push(providersFilePath(config));
875
+ if (providerResult === "updated")
876
+ result.updatedFiles.push(providersFilePath(config));
877
+ if (providerResult === "skipped")
878
+ result.skippedFiles.push(providersFilePath(config));
879
+ if (!result.files.includes(providersFilePath(config))) {
880
+ result.files.push(providersFilePath(config));
881
+ }
882
+ return result;
833
883
  }
834
884
  async function assertListenerEventExists(targetDir, names, config) {
835
885
  const file = eventFilePath(names, config);
@@ -840,6 +890,73 @@ async function assertListenerEventExists(targetDir, names, config) {
840
890
  throw new Error(`beignet make listener expected event ${names.eventName} at ${file}. Run beignet make event ${names.feature.kebab}/${names.artifact.kebab} first, or pass an event that exists.`);
841
891
  }
842
892
  }
893
+ async function updateListenerProviderWiring(targetDir, names, config, options) {
894
+ const providersFile = providersFilePath(config);
895
+ const filePath = path.join(targetDir, providersFile);
896
+ const original = await readOptionalFile(filePath);
897
+ if (original === undefined) {
898
+ throw new Error(`Could not find the generated providers file ${providersFile}. Register listeners manually with registerListeners(...), or restore the generated providers file before running make listener.`);
899
+ }
900
+ const listenerIndex = featureArtifactIndexFile("listener", names, config);
901
+ const providerName = `${names.featureSingularCamel}ListenersProvider`;
902
+ let next = original;
903
+ next = addNamedImport(next, "registerListeners", "@beignet/core/events");
904
+ next = addNamedImport(next, "createServiceActor", "@beignet/core/ports");
905
+ next = addNamedImport(next, "createProvider", "@beignet/core/providers");
906
+ next = addNamedTypeImport(next, "AppContext", aliasModule(config.paths.appContext));
907
+ next = addNamedTypeImport(next, "AppServiceContextInput", aliasModule(path.join(path.dirname(config.paths.server), "context.ts")));
908
+ next = addNamedTypeImport(next, "AppPorts", aliasModule(config.paths.ports));
909
+ const listenerImport = `import { ${listenerIndex.registryName} } from "${aliasModule(listenerIndex.path)}";`;
910
+ if (!next.includes(listenerImport)) {
911
+ next = insertAfterImports(next, listenerImport);
912
+ }
913
+ if (!new RegExp(`\\bconst\\s+${providerName}\\b`).test(next)) {
914
+ const providerDefinition = `const ${providerName} = createProvider<
915
+ \tPick<AppPorts, "eventBus" | "logger">,
916
+ \tAppContext,
917
+ \tAppServiceContextInput
918
+ >()({
919
+ \tname: "${names.feature.kebab}-listeners",
920
+ \tsetup({ ports, createServiceContext }) {
921
+ \t\tconst unregister = registerListeners(ports.eventBus, ${listenerIndex.registryName}, {
922
+ \t\t\tctx: () =>
923
+ \t\t\t\tcreateServiceContext({
924
+ \t\t\t\t\tactor: createServiceActor("beignet-listener"),
925
+ \t\t\t\t}),
926
+ \t\t\tonError(error, listener) {
927
+ \t\t\t\tports.logger.error("Event listener failed", {
928
+ \t\t\t\t\terror,
929
+ \t\t\t\t\tlistenerName: listener.name,
930
+ \t\t\t\t});
931
+ \t\t\t},
932
+ \t\t});
933
+
934
+ \t\treturn {
935
+ \t\t\tstop() {
936
+ \t\t\t\tunregister();
937
+ \t\t\t},
938
+ \t\t};
939
+ \t},
940
+ });
941
+ `;
942
+ const withProvider = next.replace(/\nexport const providers = \[/, `\n${providerDefinition}\nexport const providers = [`);
943
+ if (withProvider === next) {
944
+ throw new Error(`Could not find the exported providers array in ${providersFile}. Register ${listenerIndex.registryName} manually with registerListeners(...), or restore the generated providers file before running make listener.`);
945
+ }
946
+ next = withProvider;
947
+ }
948
+ const appended = appendToNamedArray(next, "providers", providerName);
949
+ if (appended.kind === "missing") {
950
+ throw new Error(`Could not find the exported providers array in ${providersFile}. Register ${listenerIndex.registryName} manually with registerListeners(...), or restore the generated providers file before running make listener.`);
951
+ }
952
+ if (appended.kind === "updated")
953
+ next = appended.source;
954
+ if (next === original)
955
+ return "skipped";
956
+ if (!options.dryRun)
957
+ await writeFile(filePath, next);
958
+ return "updated";
959
+ }
843
960
  export async function makeSchedule(options) {
844
961
  const targetDir = path.resolve(options.cwd ?? process.cwd());
845
962
  const names = scheduleNames(options.name, {
@@ -982,29 +1099,65 @@ async function planOutboxDrainWiring(targetDir, config, options) {
982
1099
  };
983
1100
  }
984
1101
  async function updateOutboxPortWiring(targetDir, config, options) {
985
- const updated = new Set();
1102
+ const changes = {
1103
+ createdFiles: [],
1104
+ updatedFiles: [],
1105
+ skippedFiles: [],
1106
+ };
986
1107
  if (await updateOutboxPorts(targetDir, config, options)) {
987
- updated.add(config.paths.ports);
1108
+ changes.updatedFiles.push(config.paths.ports);
988
1109
  }
989
1110
  if (await updateOutboxInfrastructurePorts(targetDir, config, options)) {
990
- updated.add(config.paths.infrastructurePorts);
1111
+ changes.updatedFiles.push(config.paths.infrastructurePorts);
991
1112
  }
992
1113
  if (await updateOutboxDatabaseProvider(targetDir, config, options)) {
993
- updated.add(path.join(infrastructureDir(config), "db/provider.ts"));
1114
+ changes.updatedFiles.push(path.join(infrastructureDir(config), "db/provider.ts"));
994
1115
  }
995
1116
  if (await updateOutboxRepositoriesReturnType(targetDir, config, options)) {
996
- updated.add(drizzleRepositoriesPath(config));
1117
+ changes.updatedFiles.push(drizzleRepositoriesPath(config));
997
1118
  }
998
- return [...updated];
1119
+ const schemaResult = await updateOutboxDrizzleSchema(targetDir, config, options);
1120
+ if (schemaResult.result === "created") {
1121
+ changes.createdFiles.push(schemaResult.path);
1122
+ }
1123
+ if (schemaResult.result === "updated") {
1124
+ changes.updatedFiles.push(schemaResult.path);
1125
+ }
1126
+ if (schemaResult.result === "skipped") {
1127
+ changes.skippedFiles.push(schemaResult.path);
1128
+ }
1129
+ if (schemaResult.canExport) {
1130
+ const schemaIndexUpdated = await updateOutboxDrizzleSchemaIndex(targetDir, config, options);
1131
+ if (schemaIndexUpdated) {
1132
+ changes.updatedFiles.push(drizzleSchemaIndexPath(config));
1133
+ }
1134
+ }
1135
+ return {
1136
+ createdFiles: uniqueStrings(changes.createdFiles),
1137
+ updatedFiles: uniqueStrings(changes.updatedFiles),
1138
+ skippedFiles: uniqueStrings(changes.skippedFiles),
1139
+ };
999
1140
  }
1000
1141
  async function applyOutboxPortWiring(result, targetDir, config, options) {
1001
- const updatedFiles = await updateOutboxPortWiring(targetDir, config, options);
1002
- for (const file of updatedFiles) {
1142
+ const changes = await updateOutboxPortWiring(targetDir, config, options);
1143
+ for (const file of changes.createdFiles) {
1144
+ if (!result.createdFiles.includes(file))
1145
+ result.createdFiles.push(file);
1146
+ if (!result.files.includes(file))
1147
+ result.files.push(file);
1148
+ }
1149
+ for (const file of changes.updatedFiles) {
1003
1150
  if (!result.updatedFiles.includes(file))
1004
1151
  result.updatedFiles.push(file);
1005
1152
  if (!result.files.includes(file))
1006
1153
  result.files.push(file);
1007
1154
  }
1155
+ for (const file of changes.skippedFiles) {
1156
+ if (!result.skippedFiles.includes(file))
1157
+ result.skippedFiles.push(file);
1158
+ if (!result.files.includes(file))
1159
+ result.files.push(file);
1160
+ }
1008
1161
  }
1009
1162
  async function updateOutboxPorts(targetDir, config, options) {
1010
1163
  const filePath = path.join(targetDir, config.paths.ports);
@@ -1082,6 +1235,42 @@ async function updateOutboxRepositoriesReturnType(targetDir, config, options) {
1082
1235
  await writeFile(filePath, next);
1083
1236
  return true;
1084
1237
  }
1238
+ async function updateOutboxDrizzleSchema(targetDir, config, options) {
1239
+ const indexPath = path.join(targetDir, drizzleSchemaIndexPath(config));
1240
+ const schemaPath = outboxDrizzleSchemaFilePath(config);
1241
+ const destination = path.join(targetDir, schemaPath);
1242
+ if (!(await fileExists(indexPath))) {
1243
+ return { path: schemaPath, canExport: false };
1244
+ }
1245
+ const database = await detectResourceDatabase(targetDir, config);
1246
+ const content = outboxDrizzleSchemaFile(drizzleDialects[database]);
1247
+ const existing = await readOptionalFile(destination);
1248
+ if (existing === undefined) {
1249
+ if (!options.dryRun) {
1250
+ await mkdir(path.dirname(destination), { recursive: true });
1251
+ await writeFile(destination, content);
1252
+ }
1253
+ return { path: schemaPath, result: "created", canExport: true };
1254
+ }
1255
+ if (existing === content ||
1256
+ /\bexport\s+const\s+outboxMessages\b/.test(existing)) {
1257
+ return { path: schemaPath, result: "skipped", canExport: true };
1258
+ }
1259
+ return { path: schemaPath, result: "skipped", canExport: false };
1260
+ }
1261
+ async function updateOutboxDrizzleSchemaIndex(targetDir, config, options) {
1262
+ const filePath = path.join(targetDir, drizzleSchemaIndexPath(config));
1263
+ if (!(await fileExists(filePath)))
1264
+ return false;
1265
+ const original = await readFile(filePath, "utf8");
1266
+ const exportLine = 'export { outboxMessages } from "./outbox-messages";';
1267
+ if (original.includes(exportLine))
1268
+ return false;
1269
+ const next = `${original.trimEnd()}\n${exportLine}\n`;
1270
+ if (!options.dryRun)
1271
+ await writeFile(filePath, next);
1272
+ return true;
1273
+ }
1085
1274
  function drizzleOutboxPortFactoryName(database) {
1086
1275
  if (database === "postgres")
1087
1276
  return "createDrizzlePostgresOutboxPort";
@@ -1358,9 +1547,12 @@ export async function makeOutbox(options = {}) {
1358
1547
  createdFiles.push(...cronSecretResult.createdFiles);
1359
1548
  updatedFiles.push(...cronSecretResult.updatedFiles);
1360
1549
  skippedFiles.push(...cronSecretResult.skippedFiles);
1361
- updatedFiles.push(...(await updateOutboxPortWiring(targetDir, config, {
1550
+ const outboxWiringResult = await updateOutboxPortWiring(targetDir, config, {
1362
1551
  dryRun: Boolean(options.dryRun),
1363
- })));
1552
+ });
1553
+ createdFiles.push(...outboxWiringResult.createdFiles);
1554
+ updatedFiles.push(...outboxWiringResult.updatedFiles);
1555
+ skippedFiles.push(...outboxWiringResult.skippedFiles);
1364
1556
  return {
1365
1557
  schemaVersion: 1,
1366
1558
  name: "outbox",
@@ -1374,6 +1566,8 @@ export async function makeOutbox(options = {}) {
1374
1566
  config.paths.infrastructurePorts,
1375
1567
  path.join(infrastructureDir(config), "db/provider.ts"),
1376
1568
  drizzleRepositoriesPath(config),
1569
+ outboxDrizzleSchemaFilePath(config),
1570
+ drizzleSchemaIndexPath(config),
1377
1571
  ],
1378
1572
  createdFiles: uniqueStrings(createdFiles),
1379
1573
  updatedFiles: uniqueStrings(updatedFiles),
@@ -3540,8 +3734,13 @@ function paymentsFiles(config, database) {
3540
3734
  ];
3541
3735
  }
3542
3736
  function featureUiComponentFiles(names, config) {
3737
+ const clientQueriesPath = featureUiClientQueriesFilePath(names, config);
3543
3738
  const componentPath = featureUiComponentFilePath(names, config);
3544
3739
  return [
3740
+ {
3741
+ path: clientQueriesPath,
3742
+ content: featureUiClientQueriesFile(names, config),
3743
+ },
3545
3744
  {
3546
3745
  path: componentPath,
3547
3746
  content: featureUiComponentFile(names, config),
@@ -3713,6 +3912,9 @@ function drizzleRepositoriesPath(config) {
3713
3912
  function drizzleResourceSchemaFilePath(names, config) {
3714
3913
  return path.join(infrastructureDir(config), "db", "schema", `${names.pluralKebab}.ts`);
3715
3914
  }
3915
+ function outboxDrizzleSchemaFilePath(config) {
3916
+ return path.join(infrastructureDir(config), "db", "schema", "outbox-messages.ts");
3917
+ }
3716
3918
  function drizzleResourceRepositoryFilePath(names, config) {
3717
3919
  return path.join(infrastructureDir(config), names.pluralKebab, `drizzle-${names.singularKebab}-repository.ts`);
3718
3920
  }
@@ -3771,6 +3973,12 @@ function resourceFeatureDir(names, config) {
3771
3973
  function featureUiDir(names, config) {
3772
3974
  return path.join(resourceFeatureDir(names, config), "components");
3773
3975
  }
3976
+ function featureUiClientDir(names, config) {
3977
+ return path.join(resourceFeatureDir(names, config), "client");
3978
+ }
3979
+ function featureUiClientQueriesFilePath(names, config) {
3980
+ return path.join(featureUiClientDir(names, config), "queries.ts");
3981
+ }
3774
3982
  function featureUiComponentFilePath(names, config) {
3775
3983
  return path.join(featureUiDir(names, config), `${names.componentFileName}.tsx`);
3776
3984
  }
@@ -4460,7 +4668,7 @@ export type List${names.pluralPascal}Input = z.infer<
4460
4668
  }
4461
4669
  function listUseCaseFile(names, config, options) {
4462
4670
  const filePath = path.join(resourceUseCaseDir(names, config), `list-${names.pluralKebab}.ts`);
4463
- return `import type { beignetServerOnly } from "@beignet/core/server-only";
4671
+ return `import "@beignet/core/server-only";
4464
4672
  import { normalizeCursorPage } from "@beignet/core/pagination";
4465
4673
  import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
4466
4674
  import {
@@ -4491,7 +4699,7 @@ ${options.tenant ? `\t\t\ttenantId,\n` : ""} });
4491
4699
  }
4492
4700
  function createUseCaseFile(names, config, options) {
4493
4701
  const filePath = path.join(resourceUseCaseDir(names, config), `create-${names.singularKebab}.ts`);
4494
- return `import type { beignetServerOnly } from "@beignet/core/server-only";
4702
+ return `import "@beignet/core/server-only";
4495
4703
  import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
4496
4704
  import {
4497
4705
  Create${names.singularPascal}InputSchema,
@@ -4514,7 +4722,7 @@ ${options.events ? `\n\t\tawait ctx.ports.eventBus.publish(${names.singularPasca
4514
4722
  }
4515
4723
  function getUseCaseFile(names, config, options) {
4516
4724
  const filePath = path.join(resourceUseCaseDir(names, config), `get-${names.singularKebab}.ts`);
4517
- return `import type { beignetServerOnly } from "@beignet/core/server-only";
4725
+ return `import "@beignet/core/server-only";
4518
4726
  import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
4519
4727
  import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
4520
4728
  import {
@@ -4540,7 +4748,7 @@ ${options.tenant ? `\t\tconst tenantId = ctx.tenant?.id;\n\t\tif (!tenantId) {\n
4540
4748
  }
4541
4749
  function updateUseCaseFile(names, config, options) {
4542
4750
  const filePath = path.join(resourceUseCaseDir(names, config), `update-${names.singularKebab}.ts`);
4543
- return `import type { beignetServerOnly } from "@beignet/core/server-only";
4751
+ return `import "@beignet/core/server-only";
4544
4752
  import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
4545
4753
  import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
4546
4754
  ${options.events ? `import { ${names.singularPascal}Updated } from "../domain/events";\n` : ""}import {
@@ -4580,7 +4788,7 @@ ${options.events ? `\n\t\tawait ctx.ports.eventBus.publish(${names.singularPasca
4580
4788
  }
4581
4789
  function deleteUseCaseFile(names, config, options) {
4582
4790
  const filePath = path.join(resourceUseCaseDir(names, config), `delete-${names.singularKebab}.ts`);
4583
- return `import type { beignetServerOnly } from "@beignet/core/server-only";
4791
+ return `import "@beignet/core/server-only";
4584
4792
  import { z } from "zod";
4585
4793
  import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
4586
4794
  import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
@@ -4673,7 +4881,7 @@ function inMemoryRepositoryFile(names, config, mode, options) {
4673
4881
  ${names.pluralCamel}.set(id, { ...existing, deletedAt: now, updatedAt: now });
4674
4882
  return true;`
4675
4883
  : `${options.tenant ? `\t\t\tconst existing = ${names.pluralCamel}.get(id);\n\t\t\tif (existing?.tenantId !== filter.tenantId) return false;\n` : ""} return ${names.pluralCamel}.delete(id);`;
4676
- return `import type { beignetServerOnly } from "@beignet/core/server-only";
4884
+ return `import "@beignet/core/server-only";
4677
4885
  import { cursorPageResult } from "@beignet/core/pagination";
4678
4886
  import type { ${names.singularPascal}Repository } from "${aliasModule(repositoryPortPath)}";
4679
4887
  import { encode${names.singularPascal}Cursor } from "${aliasModule(resourceSchemaFilePath(names, config))}";
@@ -4792,6 +5000,76 @@ ${options.tenant ? `\ttenantId: ${dialect.idColumn("tenant_id")}.notNull(),\n` :
4792
5000
  ${options.softDelete ? `\tdeletedAt: ${dialect.timestampColumn("deleted_at")},\n` : ""}});
4793
5001
  `;
4794
5002
  }
5003
+ function outboxDrizzleSchemaFile(dialect) {
5004
+ if (dialect.subpath === "mysql") {
5005
+ return `import { index, int, longtext, mysqlTable, varchar } from "drizzle-orm/mysql-core";
5006
+
5007
+ export const outboxMessages = mysqlTable(
5008
+ \t"outbox_messages",
5009
+ \t{
5010
+ \t\tid: varchar("id", { length: 255 }).primaryKey(),
5011
+ \t\tkind: varchar("kind", { length: 32 }).notNull(),
5012
+ \t\tname: varchar("name", { length: 255 }).notNull(),
5013
+ \t\tpayloadJson: longtext("payload_json").notNull(),
5014
+ \t\tstatus: varchar("status", { length: 32 }).notNull(),
5015
+ \t\tattempts: int("attempts").notNull().default(0),
5016
+ \t\tmaxAttempts: int("max_attempts").notNull().default(3),
5017
+ \t\tavailableAt: varchar("available_at", { length: 32 }).notNull(),
5018
+ \t\tclaimedAt: varchar("claimed_at", { length: 32 }),
5019
+ \t\tlockedUntil: varchar("locked_until", { length: 32 }),
5020
+ \t\tclaimToken: varchar("claim_token", { length: 64 }),
5021
+ \t\tdeliveredAt: varchar("delivered_at", { length: 32 }),
5022
+ \t\tlastErrorJson: longtext("last_error_json"),
5023
+ \t\tcreatedAt: varchar("created_at", { length: 32 }).notNull(),
5024
+ \t\tupdatedAt: varchar("updated_at", { length: 32 }).notNull(),
5025
+ \t},
5026
+ \t(table) => ({
5027
+ \t\tavailableIdx: index("outbox_messages_available_idx").on(
5028
+ \t\t\ttable.status,
5029
+ \t\t\ttable.availableAt,
5030
+ \t\t),
5031
+ \t\tlockedIdx: index("outbox_messages_locked_idx").on(
5032
+ \t\t\ttable.status,
5033
+ \t\t\ttable.lockedUntil,
5034
+ \t\t),
5035
+ \t}),
5036
+ );
5037
+ `;
5038
+ }
5039
+ return `import { index, integer, ${dialect.tableFunction}, text } from "${dialect.columnModule}";
5040
+
5041
+ export const outboxMessages = ${dialect.tableFunction}(
5042
+ \t"outbox_messages",
5043
+ \t{
5044
+ \t\tid: text("id").primaryKey(),
5045
+ \t\tkind: text("kind").notNull(),
5046
+ \t\tname: text("name").notNull(),
5047
+ \t\tpayloadJson: text("payload_json").notNull(),
5048
+ \t\tstatus: text("status").notNull(),
5049
+ \t\tattempts: integer("attempts").notNull().default(0),
5050
+ \t\tmaxAttempts: integer("max_attempts").notNull().default(3),
5051
+ \t\tavailableAt: text("available_at").notNull(),
5052
+ \t\tclaimedAt: text("claimed_at"),
5053
+ \t\tlockedUntil: text("locked_until"),
5054
+ \t\tclaimToken: text("claim_token"),
5055
+ \t\tdeliveredAt: text("delivered_at"),
5056
+ \t\tlastErrorJson: text("last_error_json"),
5057
+ \t\tcreatedAt: text("created_at").notNull(),
5058
+ \t\tupdatedAt: text("updated_at").notNull(),
5059
+ \t},
5060
+ \t(table) => ({
5061
+ \t\tavailableIdx: index("outbox_messages_available_idx").on(
5062
+ \t\t\ttable.status,
5063
+ \t\t\ttable.availableAt,
5064
+ \t\t),
5065
+ \t\tlockedIdx: index("outbox_messages_locked_idx").on(
5066
+ \t\t\ttable.status,
5067
+ \t\t\ttable.lockedUntil,
5068
+ \t\t),
5069
+ \t}),
5070
+ );
5071
+ `;
5072
+ }
4795
5073
  function drizzleResourceWhere(names, options, predicates) {
4796
5074
  const allPredicates = [
4797
5075
  ...predicates,
@@ -4961,7 +5239,7 @@ ${options.softDelete ? `\t\t\tconst now = new Date().toISOString();\n\t\t\tconst
4961
5239
  return affectedRows(result) > 0;
4962
5240
  },
4963
5241
  `;
4964
- return `import type { beignetServerOnly } from "@beignet/core/server-only";
5242
+ return `import "@beignet/core/server-only";
4965
5243
  import { cursorPageResult } from "@beignet/core/pagination";
4966
5244
  import type { ${dialect.dbTypeName} } from "@beignet/provider-db-drizzle/${dialect.subpath}";
4967
5245
  import { ${drizzleImports.join(", ")} } from "drizzle-orm";
@@ -5182,7 +5460,7 @@ export const ${names.pluralCamel}Contracts = [list${names.pluralPascal}];
5182
5460
  `;
5183
5461
  }
5184
5462
  function standaloneUseCaseFile(names, config, filePath) {
5185
- return `import type { beignetServerOnly } from "@beignet/core/server-only";
5463
+ return `import "@beignet/core/server-only";
5186
5464
  import { z } from "zod";
5187
5465
  import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
5188
5466
 
@@ -6950,28 +7228,30 @@ export const { POST } = createPaymentWebhookRoute({
6950
7228
  `;
6951
7229
  }
6952
7230
  function featureUiComponentFile(names, config) {
7231
+ const componentPath = featureUiComponentFilePath(names, config);
7232
+ const clientQueriesPath = featureUiClientQueriesFilePath(names, config);
6953
7233
  return `"use client";
6954
7234
 
6955
7235
  import { contractErrorMessage } from "@beignet/core/client";
6956
7236
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
6957
7237
  import { useState } from "react";
6958
- import { rq } from "${aliasModule(clientIndexPath(config))}";
6959
- import { create${names.singularPascal}, list${names.pluralPascal} } from "${aliasModule(resourceContractFilePath(names, config))}";
7238
+ import {
7239
+ \tcreate${names.singularPascal}MutationOptions,
7240
+ \tinvalidate${names.pluralPascal},
7241
+ \tlist${names.pluralPascal}QueryOptions,
7242
+ } from "${relativeModule(componentPath, clientQueriesPath)}";
6960
7243
 
6961
7244
  export function ${names.componentExportName}() {
6962
7245
  \tconst [name, setName] = useState("");
6963
7246
  \tconst queryClient = useQueryClient();
6964
- \tconst ${names.pluralCamel}Query = useQuery(
6965
- \t\trq(list${names.pluralPascal}).queryOptions({ query: {} }),
6966
- \t);
6967
- \tconst create${names.singularPascal}Mutation = useMutation(
6968
- \t\trq(create${names.singularPascal}).mutationOptions({
6969
- \t\t\tonSuccess: async () => {
6970
- \t\t\t\tsetName("");
6971
- \t\t\t\tawait rq(list${names.pluralPascal}).invalidate(queryClient);
6972
- \t\t\t},
6973
- \t\t}),
6974
- \t);
7247
+ \tconst ${names.pluralCamel}Query = useQuery(list${names.pluralPascal}QueryOptions());
7248
+ \tconst create${names.singularPascal}Mutation = useMutation({
7249
+ \t\t...create${names.singularPascal}MutationOptions(),
7250
+ \t\tonSuccess: async () => {
7251
+ \t\t\tsetName("");
7252
+ \t\t\tawait invalidate${names.pluralPascal}(queryClient);
7253
+ \t\t},
7254
+ \t});
6975
7255
 
6976
7256
  \treturn (
6977
7257
  \t\t<section className="${names.pluralKebab}-panel">
@@ -7038,6 +7318,29 @@ export function ${names.componentExportName}() {
7038
7318
  }
7039
7319
  `;
7040
7320
  }
7321
+ function featureUiClientQueriesFile(names, config) {
7322
+ const clientQueriesPath = featureUiClientQueriesFilePath(names, config);
7323
+ const contractsPath = resourceContractFilePath(names, config);
7324
+ return `import type { QueryClient } from "@tanstack/react-query";
7325
+ import { rq } from "${aliasModule(clientIndexPath(config))}";
7326
+ import {
7327
+ \tcreate${names.singularPascal},
7328
+ \tlist${names.pluralPascal},
7329
+ } from "${relativeModule(clientQueriesPath, contractsPath)}";
7330
+
7331
+ export function list${names.pluralPascal}QueryOptions() {
7332
+ \treturn rq(list${names.pluralPascal}).queryOptions({ query: {} });
7333
+ }
7334
+
7335
+ export function create${names.singularPascal}MutationOptions() {
7336
+ \treturn rq(create${names.singularPascal}).mutationOptions();
7337
+ }
7338
+
7339
+ export function invalidate${names.pluralPascal}(queryClient: QueryClient) {
7340
+ \treturn rq(list${names.pluralPascal}).invalidate(queryClient);
7341
+ }
7342
+ `;
7343
+ }
7041
7344
  function scheduleRouteFile(names, config) {
7042
7345
  return `import { createScheduleRoute } from "@beignet/next";
7043
7346
  import { ${names.scheduleExportName} } from "${aliasModule(scheduleFilePath(names, config))}";
@@ -7069,7 +7372,7 @@ function routeGroupFile(names, config, mode, _options) {
7069
7372
  const routeFilePath = path.join(config.paths.features, names.pluralKebab, "routes.ts");
7070
7373
  const contractsPath = resourceContractFilePath(names, config);
7071
7374
  const useCasesPath = resourceUseCaseIndexPath(names, config);
7072
- return `import type { beignetServerOnly } from "@beignet/core/server-only";
7375
+ return `import "@beignet/core/server-only";
7073
7376
  import { defineRouteGroup } from "@beignet/next";
7074
7377
  import type { AppContext } from "${relativeModule(routeFilePath, config.paths.appContext)}";
7075
7378
  import {
@@ -7093,11 +7396,11 @@ ${mode === "resource" ? ` { contract: get${names.singularPascal}, useCase: get$
7093
7396
  function testFile(names, config, mode, options) {
7094
7397
  const repositoryPath = path.join(path.dirname(config.paths.infrastructurePorts), names.pluralKebab, `in-memory-${names.singularKebab}-repository.ts`);
7095
7398
  const serverContextPath = path.join(path.dirname(config.paths.server), "context.ts");
7096
- const requestTarget = options.tenant ? "requester" : "app";
7399
+ const requestTarget = "app";
7097
7400
  return `import { describe, expect, it } from "bun:test";
7098
7401
  import { createUseCaseTester } from "@beignet/core/application";
7099
7402
  ${mode === "resource" ? `import { isAppError } from "@beignet/core/errors";\n` : ""}import { defineRoutes } from "@beignet/web";
7100
- import { createTestApp${options.tenant ? ", createTestRequester" : ""} } from "@beignet/web/testing";
7403
+ import { createTestApp } from "@beignet/web/testing";
7101
7404
  import { createInMemoryDevtools } from "@beignet/devtools";
7102
7405
  import { createTestContextFactory, createTestPorts } from "@beignet/core/testing";
7103
7406
  import { ${options.auth ? "createTestUserActor" : "createTestAnonymousActor"}${options.tenant ? ", createTestTenant" : ""} } from "@beignet/core/ports/testing";
@@ -7145,13 +7448,15 @@ ${options.tenant ? `\t\t\t{\n\t\t\t\tid: "00000000-0000-4000-8000-000000000104",
7145
7448
  base: appPorts,
7146
7449
  overrides: {
7147
7450
  ${names.pluralCamel},
7148
- ${options.auth
7451
+ ${options.auth || options.tenant
7149
7452
  ? `\t\t\t\tauth: {
7150
7453
  getSession: async () => ({
7151
- user: { id: "user_test", name: "Test User" },
7454
+ user: { id: "user_test", name: "Test User"${options.tenant ? `, tenantId: "tenant_test"` : ""} },
7455
+ ${options.tenant ? `\t\t\t\t\t\tsession: { tenantId: "tenant_test" },\n` : ""}
7152
7456
  }),
7153
7457
  },
7154
- gate: appPorts.gate,\n`
7458
+ ${options.auth ? `\t\t\t\tgate: appPorts.gate,\n` : ""}
7459
+ `
7155
7460
  : `\t\t\t\tauth: {
7156
7461
  getSession: async () => null,
7157
7462
  },\n`} devtools: createInMemoryDevtools(),
@@ -7298,7 +7603,6 @@ ${mode === "resource"
7298
7603
  routes: defineRoutes<AppContext>([${names.singularCamel}Routes]),
7299
7604
  context: appContext,
7300
7605
  });
7301
- ${options.tenant ? `\t\tconst requester = createTestRequester(app, {\n\t\t\theaders: { "x-tenant-id": "tenant_test" },\n\t\t});\n` : ""}
7302
7606
  const createdViaRoute = await ${requestTarget}.request(create${names.singularPascal}, {
7303
7607
  body: { name: "Second ${names.singularPascal}" },
7304
7608
  });