@beignet/cli 0.0.31 → 0.0.33

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 (107) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/README.md +70 -17
  3. package/dist/check.d.ts +2 -0
  4. package/dist/check.d.ts.map +1 -1
  5. package/dist/check.js +16 -0
  6. package/dist/check.js.map +1 -1
  7. package/dist/choices.d.ts +22 -2
  8. package/dist/choices.d.ts.map +1 -1
  9. package/dist/choices.js +62 -0
  10. package/dist/choices.js.map +1 -1
  11. package/dist/config.d.ts +2 -0
  12. package/dist/config.d.ts.map +1 -1
  13. package/dist/config.js +2 -0
  14. package/dist/config.js.map +1 -1
  15. package/dist/create-prompts.d.ts +6 -2
  16. package/dist/create-prompts.d.ts.map +1 -1
  17. package/dist/create-prompts.js +1 -1
  18. package/dist/create-prompts.js.map +1 -1
  19. package/dist/db.d.ts +5 -5
  20. package/dist/db.d.ts.map +1 -1
  21. package/dist/db.js +5 -5
  22. package/dist/db.js.map +1 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +386 -27
  25. package/dist/index.js.map +1 -1
  26. package/dist/inspect.d.ts +5 -0
  27. package/dist/inspect.d.ts.map +1 -1
  28. package/dist/inspect.js +805 -62
  29. package/dist/inspect.js.map +1 -1
  30. package/dist/make/inbox.d.ts.map +1 -1
  31. package/dist/make/inbox.js +5 -3
  32. package/dist/make/inbox.js.map +1 -1
  33. package/dist/make/payments.d.ts.map +1 -1
  34. package/dist/make/payments.js +58 -29
  35. package/dist/make/payments.js.map +1 -1
  36. package/dist/make/shared.d.ts +12 -3
  37. package/dist/make/shared.d.ts.map +1 -1
  38. package/dist/make/shared.js +52 -11
  39. package/dist/make/shared.js.map +1 -1
  40. package/dist/make/tenancy.d.ts.map +1 -1
  41. package/dist/make/tenancy.js +6 -4
  42. package/dist/make/tenancy.js.map +1 -1
  43. package/dist/make.d.ts.map +1 -1
  44. package/dist/make.js +136 -65
  45. package/dist/make.js.map +1 -1
  46. package/dist/outbox.d.ts +114 -1
  47. package/dist/outbox.d.ts.map +1 -1
  48. package/dist/outbox.js +190 -0
  49. package/dist/outbox.js.map +1 -1
  50. package/dist/preflight.d.ts +90 -0
  51. package/dist/preflight.d.ts.map +1 -0
  52. package/dist/preflight.js +423 -0
  53. package/dist/preflight.js.map +1 -0
  54. package/dist/provider-add.d.ts +10 -1
  55. package/dist/provider-add.d.ts.map +1 -1
  56. package/dist/provider-add.js +255 -16
  57. package/dist/provider-add.js.map +1 -1
  58. package/dist/task.d.ts.map +1 -1
  59. package/dist/task.js +2 -3
  60. package/dist/task.js.map +1 -1
  61. package/dist/templates/agents.d.ts.map +1 -1
  62. package/dist/templates/agents.js +6 -3
  63. package/dist/templates/agents.js.map +1 -1
  64. package/dist/templates/base.d.ts.map +1 -1
  65. package/dist/templates/base.js +5 -11
  66. package/dist/templates/base.js.map +1 -1
  67. package/dist/templates/index.d.ts.map +1 -1
  68. package/dist/templates/index.js +2 -0
  69. package/dist/templates/index.js.map +1 -1
  70. package/dist/templates/server.d.ts.map +1 -1
  71. package/dist/templates/server.js +10 -5
  72. package/dist/templates/server.js.map +1 -1
  73. package/dist/templates/shared.d.ts +5 -1
  74. package/dist/templates/shared.d.ts.map +1 -1
  75. package/dist/templates/shared.js +13 -1
  76. package/dist/templates/shared.js.map +1 -1
  77. package/dist/templates/testing.d.ts +5 -0
  78. package/dist/templates/testing.d.ts.map +1 -0
  79. package/dist/templates/testing.js +542 -0
  80. package/dist/templates/testing.js.map +1 -0
  81. package/dist/templates/todos.js +2 -2
  82. package/package.json +3 -2
  83. package/skills/app-structure/SKILL.md +12 -4
  84. package/src/check.ts +23 -0
  85. package/src/choices.ts +84 -0
  86. package/src/config.ts +4 -0
  87. package/src/create-prompts.ts +8 -2
  88. package/src/db.ts +12 -12
  89. package/src/index.ts +592 -37
  90. package/src/inspect.ts +1253 -156
  91. package/src/make/inbox.ts +5 -3
  92. package/src/make/payments.ts +58 -29
  93. package/src/make/shared.ts +87 -13
  94. package/src/make/tenancy.ts +6 -4
  95. package/src/make.ts +179 -65
  96. package/src/outbox.ts +363 -0
  97. package/src/preflight.ts +596 -0
  98. package/src/provider-add.ts +270 -16
  99. package/src/task.ts +2 -3
  100. package/src/templates/agents.ts +6 -3
  101. package/src/templates/base.ts +5 -13
  102. package/src/templates/index.ts +2 -0
  103. package/src/templates/server.ts +10 -5
  104. package/src/templates/shared.ts +15 -1
  105. package/src/templates/testing.ts +545 -0
  106. package/src/templates/todos.ts +2 -2
  107. package/src/test-helpers/generated-app.ts +10 -6
package/dist/make.js CHANGED
@@ -2,8 +2,9 @@ import { randomBytes } from "node:crypto";
2
2
  import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { clientIndexPath, loadBeignetConfig, resolveConfig, } from "./config.js";
5
- import { adapterFilePath, addNamedImport, addNamedTypeImport, aliasModule, appendDeferredPortKey, appendPickStringLiteralMember, applyPortProviderWiring, assertAdapterApp, assertFeatureArtifactApp, assertFeatureUiApp, assertGeneratedIdentifiers, assertPersistenceArtifactApp, assertPolicyApp, assertPortApp, assertServerRuntimeApp, assertStandardApp, assertTestApp, assertUseCaseApp, beignetDependencyVersion, constantCase, defineUploadsInitializerInfo, detectResourceDatabase, detectResourcePersistence, drizzleDialects, drizzleRepositoriesPath, drizzleResourceRepositoryFilePath, drizzleSchemaIndexPath, eventBusPortWiring, eventFilePath, factoryFilePath, featureArtifactDir, featureArtifactIndexFile, featureArtifactNames, featureResourceNames, featureUiClientQueriesFilePath, featureUiComponentFilePath, featureUiIndexFilePath, featureUiNames, fileExists, hasTopLevelObjectProperty, identifierNames, infrastructureDir, insertTypeProperty, jobFilePath, listenerFilePath, mergeMakeResultInto, mergeMakeResults, missingCapabilityBuilderFiles, notificationFilePath, notificationPortWirings, planGeneratedFile, planGeneratedFiles, portFilePath, portNames, providersFilePath, readOptionalFile, relativeModule, resourceContractFilePath, resourceFeatureDir, resourceNames, resourcePortFilePath, resourceSharedErrorsPath, scheduleFilePath, seedEntrypointPath, seedFilePath, seedNames, singularize, storagePortWiring, tanstackReactQueryDependencyVersion, taskFilePath, typeBodySource, uniqueStrings, updateDrizzleRepositories, updateDrizzleSchemaIndex, updateFeatureArtifactIndex, updateInfrastructureAdapterWiring, updateInfrastructureDeferredPorts, updateInfrastructureGatePolicies, updateInfrastructurePortStub, updateInfrastructurePorts, updateOpenApiRoute, updatePackageDependencies, updatePackageJson, updatePackageScripts, updatePortIndex, updatePortsIndex, updateSeedEntrypoint, updateServerRoutes, updateServerTransactionPorts, updateSharedErrors, uploadFilePath, useCaseFeatureDir, useCaseFilePath, usesFeatureOwnedPolicies, usesFeatureOwnedTests, wireListenersProviderSource, wirePortProviders, writeGeneratedFile, writePlannedGeneratedFile, } from "./make/shared.js";
5
+ import { adapterFilePath, addNamedImport, addNamedTypeImport, aliasModule, appendDeferredPortKey, appendPickStringLiteralMember, applyPortProviderWiring, assertAdapterApp, assertFeatureArtifactApp, assertFeatureUiApp, assertGeneratedIdentifiers, assertPersistenceArtifactApp, assertPolicyApp, assertPortApp, assertServerRuntimeApp, assertStandardApp, assertTestApp, assertUseCaseApp, beignetDependencyVersion, constantCase, createListenersRegistrySource, defineUploadsInitializerInfo, detectResourceDatabase, detectResourcePersistence, drizzleDialects, drizzleRepositoriesPath, drizzleResourceRepositoryFilePath, drizzleSchemaIndexPath, eventBusPortWiring, eventFilePath, factoryFilePath, featureArtifactDir, featureArtifactIndexFile, featureArtifactNames, featureResourceNames, featureUiClientQueriesFilePath, featureUiComponentFilePath, featureUiIndexFilePath, featureUiNames, fileExists, hasTopLevelObjectProperty, identifierNames, infrastructureDir, insertTypeProperty, jobFilePath, listenerFilePath, listenersFilePath, mergeMakeResultInto, mergeMakeResults, missingCapabilityBuilderFiles, notificationFilePath, notificationPortWirings, planGeneratedFile, planGeneratedFiles, portFilePath, portNames, providersFilePath, readOptionalFile, relativeModule, resourceContractFilePath, resourceFeatureDir, resourceNames, resourcePortFilePath, resourceSharedErrorsPath, scheduleFilePath, seedEntrypointPath, seedFilePath, seedNames, singularize, storagePortWiring, tanstackReactQueryDependencyVersion, taskFilePath, typeBodySource, uniqueStrings, updateDrizzleRepositories, updateDrizzleSchemaIndex, updateFeatureArtifactIndex, updateInfrastructureAdapterWiring, updateInfrastructureDeferredPorts, updateInfrastructureGatePolicies, updateInfrastructurePortStub, updateInfrastructurePorts, updateListenersRegistrySource, updateOpenApiRoute, updatePackageDependencies, updatePackageJson, updatePackageScripts, updatePortIndex, updatePortsIndex, updateSeedEntrypoint, updateServerRoutes, updateServerTransactionPorts, updateSharedErrors, uploadFilePath, useCaseFeatureDir, useCaseFilePath, usesFeatureOwnedPolicies, usesFeatureOwnedTests, wireListenersProviderSource, wirePortProviders, writeGeneratedFile, writePlannedGeneratedFile, } from "./make/shared.js";
6
6
  import { appendToArrayExpression, appendToOutboxRegistryArray, arrayInitializerInfo, identifiersFromArrayExpression, insertAfterImports, } from "./registry-edits.js";
7
+ import { testSupportTemplateFiles } from "./templates/testing.js";
7
8
  export { makeFeatureAddonChoices, makeFeatureRecipeChoices, } from "./choices.js";
8
9
  export * from "./make/inbox.js";
9
10
  export * from "./make/payments.js";
@@ -689,7 +690,7 @@ export async function makeSeed(options) {
689
690
  if (!result.files.includes(seedEntrypointPath(config))) {
690
691
  result.files.push(seedEntrypointPath(config));
691
692
  }
692
- if (await updatePackageScripts(targetDir, { "db:seed": `bun ${seedEntrypointPath(config)}` }, { dryRun: Boolean(options.dryRun) })) {
693
+ if (await updatePackageScripts(targetDir, { "db:seed": `tsx ${seedEntrypointPath(config)}` }, { dryRun: Boolean(options.dryRun) })) {
693
694
  result.updatedFiles.push("package.json");
694
695
  if (!result.files.includes("package.json"))
695
696
  result.files.push("package.json");
@@ -742,6 +743,9 @@ export async function makeListener(options) {
742
743
  if (!options.skipEventAssert) {
743
744
  await assertListenerEventExists(targetDir, names.event, config);
744
745
  }
746
+ await updateListenerRegistry(targetDir, names, config, {
747
+ dryRun: true,
748
+ });
745
749
  await updateListenerProviderWiring(targetDir, names, config, {
746
750
  dryRun: true,
747
751
  });
@@ -760,6 +764,18 @@ export async function makeListener(options) {
760
764
  "@beignet/core": beignetDependencyVersion,
761
765
  },
762
766
  });
767
+ const registryResult = await updateListenerRegistry(targetDir, names, config, {
768
+ dryRun: Boolean(options.dryRun),
769
+ });
770
+ if (registryResult === "created")
771
+ result.createdFiles.push(listenersFilePath(config));
772
+ if (registryResult === "updated")
773
+ result.updatedFiles.push(listenersFilePath(config));
774
+ if (registryResult === "skipped")
775
+ result.skippedFiles.push(listenersFilePath(config));
776
+ if (!result.files.includes(listenersFilePath(config))) {
777
+ result.files.push(listenersFilePath(config));
778
+ }
763
779
  const providerResult = await updateListenerProviderWiring(targetDir, names, config, {
764
780
  dryRun: Boolean(options.dryRun),
765
781
  });
@@ -774,6 +790,36 @@ export async function makeListener(options) {
774
790
  }
775
791
  return result;
776
792
  }
793
+ async function updateListenerRegistry(targetDir, names, config, options) {
794
+ const file = listenersFilePath(config);
795
+ const filePath = path.join(targetDir, file);
796
+ const listenerIndex = featureArtifactIndexFile("listener", names, config);
797
+ const original = await readOptionalFile(filePath);
798
+ if (original === undefined) {
799
+ if (!options.dryRun) {
800
+ await writeGeneratedFile(targetDir, {
801
+ path: file,
802
+ content: createListenersRegistrySource({
803
+ registryName: listenerIndex.registryName,
804
+ listenerModule: aliasModule(listenerIndex.path),
805
+ }),
806
+ }, { force: false, dryRun: false });
807
+ }
808
+ return "created";
809
+ }
810
+ const result = updateListenersRegistrySource(original, {
811
+ registryName: listenerIndex.registryName,
812
+ listenerModule: aliasModule(listenerIndex.path),
813
+ });
814
+ if (result.kind === "missing") {
815
+ throw new Error(`Could not find the exported listeners array in ${file}. Register ${listenerIndex.registryName} manually, or restore the generated listeners file before running make listener.`);
816
+ }
817
+ if (result.kind === "unchanged")
818
+ return "skipped";
819
+ if (!options.dryRun)
820
+ await writeFile(filePath, result.source);
821
+ return "updated";
822
+ }
777
823
  async function assertListenerEventExists(targetDir, names, config) {
778
824
  const file = eventFilePath(names, config);
779
825
  try {
@@ -1012,9 +1058,13 @@ async function updateOutboxPorts(targetDir, config, options) {
1012
1058
  const filePath = path.join(targetDir, config.paths.ports);
1013
1059
  const original = await readFile(filePath, "utf8");
1014
1060
  let next = addNamedTypeImport(original, "OutboxPort", "@beignet/core/outbox");
1061
+ next = addNamedTypeImport(next, "OutboxAdminPort", "@beignet/core/outbox");
1015
1062
  if (!typeHasTopLevelProperty(next, "AppPorts", "outbox")) {
1016
1063
  next = insertTypeProperty(next, "AppPorts", "outbox: OutboxPort;", `Could not find AppPorts in ${config.paths.ports}. Add outbox: OutboxPort; manually, or restore the generated ports file before running make outbox.`);
1017
1064
  }
1065
+ if (!typeHasTopLevelProperty(next, "AppPorts", "outboxAdmin")) {
1066
+ next = insertTypeProperty(next, "AppPorts", "outboxAdmin: OutboxAdminPort;", `Could not find AppPorts in ${config.paths.ports}. Add outboxAdmin: OutboxAdminPort; manually, or restore the generated ports file before running make outbox.`);
1067
+ }
1018
1068
  if (!typeHasTopLevelProperty(next, "AppTransactionPorts", "outbox")) {
1019
1069
  next = insertTypeProperty(next, "AppTransactionPorts", "outbox: OutboxPort;", `Could not find AppTransactionPorts in ${config.paths.ports}. Add outbox: OutboxPort; manually, or restore the generated ports file before running make outbox.`);
1020
1070
  }
@@ -1027,7 +1077,8 @@ async function updateOutboxPorts(targetDir, config, options) {
1027
1077
  async function updateOutboxInfrastructurePorts(targetDir, config, options) {
1028
1078
  const filePath = path.join(targetDir, config.paths.infrastructurePorts);
1029
1079
  const original = await readFile(filePath, "utf8");
1030
- const next = appendDeferredPortKey(original, "outbox");
1080
+ let next = appendDeferredPortKey(original, "outbox");
1081
+ next = appendDeferredPortKey(next, "outboxAdmin");
1031
1082
  if (next === original)
1032
1083
  return false;
1033
1084
  if (!options.dryRun)
@@ -1041,9 +1092,12 @@ async function updateOutboxDatabaseProvider(targetDir, config, options) {
1041
1092
  return false;
1042
1093
  const database = await detectResourceDatabase(targetDir, config);
1043
1094
  const factoryName = drizzleOutboxPortFactoryName(database);
1095
+ const adminFactoryName = drizzleOutboxAdminPortFactoryName(database);
1044
1096
  const original = await readFile(filePath, "utf8");
1045
1097
  let next = addNamedImport(original, factoryName, `@beignet/provider-db-drizzle/${database}`);
1098
+ next = addNamedImport(next, adminFactoryName, `@beignet/provider-db-drizzle/${database}`);
1046
1099
  next = appendPickStringLiteralMember(next, "AppPorts", "outbox");
1100
+ next = appendPickStringLiteralMember(next, "AppPorts", "outboxAdmin");
1047
1101
  if (!/\bconst\s+outbox\s*=/.test(next)) {
1048
1102
  const beforeOutboxConst = next;
1049
1103
  next = beforeOutboxConst.replace(/(\n\s*const idempotency = createDrizzle[A-Za-z]+IdempotencyPort\((dbPort\.(?:drizzle|db))\);)/, `$1\n\t\tconst outbox = ${factoryName}($2);`);
@@ -1051,6 +1105,13 @@ async function updateOutboxDatabaseProvider(targetDir, config, options) {
1051
1105
  throw new Error(`Could not find the idempotency port in ${file}. Add const outbox = ${factoryName}(dbPort.drizzle) manually, or restore the generated database provider before running make outbox.`);
1052
1106
  }
1053
1107
  }
1108
+ if (!/\bconst\s+outboxAdmin\s*=/.test(next)) {
1109
+ const beforeOutboxAdminConst = next;
1110
+ next = beforeOutboxAdminConst.replace(new RegExp(`(\\n\\s*const outbox = ${factoryName}\\((dbPort\\.(?:drizzle|db))\\);)`), `$1\n\t\tconst outboxAdmin = ${adminFactoryName}($2);`);
1111
+ if (next === beforeOutboxAdminConst) {
1112
+ throw new Error(`Could not find the outbox port in ${file}. Add const outboxAdmin = ${adminFactoryName}(dbPort.drizzle) manually, or restore the generated database provider before running make outbox.`);
1113
+ }
1114
+ }
1054
1115
  if (!/\boutbox\s*,/.test(next)) {
1055
1116
  const withOutboxPort = next.replace(/(\n\s*idempotency,\n)/, `$1\t\t\toutbox,\n`);
1056
1117
  if (withOutboxPort === next) {
@@ -1058,6 +1119,13 @@ async function updateOutboxDatabaseProvider(targetDir, config, options) {
1058
1119
  }
1059
1120
  next = withOutboxPort;
1060
1121
  }
1122
+ if (!/\boutboxAdmin\s*,/.test(next)) {
1123
+ const withOutboxAdminPort = next.replace(/(\n\s*outbox,\n)/, `$1\t\t\toutboxAdmin,\n`);
1124
+ if (withOutboxAdminPort === next) {
1125
+ throw new Error(`Could not find outbox in providedPorts in ${file}. Add outboxAdmin to providedPorts manually, or restore the generated database provider before running make outbox.`);
1126
+ }
1127
+ next = withOutboxAdminPort;
1128
+ }
1061
1129
  if (!new RegExp(`outbox:\\s*${factoryName}\\(tx\\)`).test(next)) {
1062
1130
  const withTransactionOutbox = next.replace(/(\n\s*idempotency: createDrizzle[A-Za-z]+IdempotencyPort\(tx\),\n)/, `$1\t\t\t\t\toutbox: ${factoryName}(tx),\n`);
1063
1131
  if (withTransactionOutbox === next) {
@@ -1127,6 +1195,13 @@ function drizzleOutboxPortFactoryName(database) {
1127
1195
  return "createDrizzleMysqlOutboxPort";
1128
1196
  return "createDrizzleSqliteOutboxPort";
1129
1197
  }
1198
+ function drizzleOutboxAdminPortFactoryName(database) {
1199
+ if (database === "postgres")
1200
+ return "createDrizzlePostgresOutboxAdminPort";
1201
+ if (database === "mysql")
1202
+ return "createDrizzleMysqlOutboxAdminPort";
1203
+ return "createDrizzleSqliteOutboxAdminPort";
1204
+ }
1130
1205
  async function applyOutboxDrainWiring(result, targetDir, plan, options) {
1131
1206
  if (!plan)
1132
1207
  return;
@@ -1180,7 +1255,10 @@ export async function makeUpload(options) {
1180
1255
  force: Boolean(options.force),
1181
1256
  dryRun: Boolean(options.dryRun),
1182
1257
  name: `${names.feature.kebab}/${names.artifact.kebab}`,
1183
- files: uploadFiles(names, config),
1258
+ files: [
1259
+ ...(await missingCapabilityBuilderFiles(targetDir, "uploads", config)),
1260
+ ...uploadFiles(names, config),
1261
+ ],
1184
1262
  index: featureArtifactIndexFile("upload", names, config),
1185
1263
  dependencies: {
1186
1264
  "@beignet/core": beignetDependencyVersion,
@@ -1581,6 +1659,7 @@ function resourceFiles(names, config, persistence = "memory", mode = "feature",
1581
1659
  const infraDir = infrastructureDir(config);
1582
1660
  const featureDir = resourceFeatureDir(names, config);
1583
1661
  const files = [
1662
+ ...testSupportTemplateFiles,
1584
1663
  {
1585
1664
  path: resourceContractFilePath(names, config),
1586
1665
  content: contractFile(names, config, mode, options),
@@ -1706,6 +1785,7 @@ function useCaseFiles(names, config) {
1706
1785
  }
1707
1786
  function testFiles(names, config) {
1708
1787
  return [
1788
+ ...testSupportTemplateFiles,
1709
1789
  {
1710
1790
  path: useCaseTestFilePath(names, config),
1711
1791
  content: useCaseTestFile(names, config),
@@ -2258,7 +2338,7 @@ function listUseCaseFile(names, config, options) {
2258
2338
  const filePath = path.join(resourceUseCaseDir(names, config), `list-${names.pluralKebab}.ts`);
2259
2339
  return `import "@beignet/core/server-only";
2260
2340
  import { normalizeCursorPage } from "@beignet/core/pagination";
2261
- import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
2341
+ ${options.tenant ? `import { requireTenantScope } from "@beignet/core/tenancy";\n` : ""}import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
2262
2342
  import {
2263
2343
  decode${names.singularPascal}Cursor,
2264
2344
  List${names.pluralPascal}InputSchema,
@@ -2270,7 +2350,7 @@ export const list${names.pluralPascal}UseCase = useCase
2270
2350
  .input(List${names.pluralPascal}InputSchema)
2271
2351
  .output(List${names.pluralPascal}OutputSchema)
2272
2352
  .run(async ({ ctx, input }) => {
2273
- ${options.tenant ? `\t\tconst tenantId = ctx.tenant?.id;\n\t\tif (!tenantId) {\n\t\t\tthrow new Error("Tenant is required to list ${names.pluralKebab}.");\n\t\t}\n\n` : ""} const page = normalizeCursorPage(input, {
2353
+ ${options.tenant ? `\t\tconst scope = requireTenantScope(ctx);\n\n` : ""} const page = normalizeCursorPage(input, {
2274
2354
  defaultLimit: 20,
2275
2355
  maxLimit: 100,
2276
2356
  });
@@ -2281,14 +2361,14 @@ ${options.tenant ? `\t\tconst tenantId = ctx.tenant?.id;\n\t\tif (!tenantId) {\n
2281
2361
  name: input.name,
2282
2362
  sortBy: input.sortBy,
2283
2363
  sortDirection: input.sortDirection,
2284
- ${options.tenant ? `\t\t\ttenantId,\n` : ""} });
2364
+ }${options.tenant ? ", scope" : ""});
2285
2365
  });
2286
2366
  `;
2287
2367
  }
2288
2368
  function createUseCaseFile(names, config, options) {
2289
2369
  const filePath = path.join(resourceUseCaseDir(names, config), `create-${names.singularKebab}.ts`);
2290
2370
  return `import "@beignet/core/server-only";
2291
- import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
2371
+ ${options.tenant ? `import { requireTenantScope } from "@beignet/core/tenancy";\n` : ""}import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
2292
2372
  import {
2293
2373
  Create${names.singularPascal}InputSchema,
2294
2374
  ${names.singularPascal}Schema,
@@ -2300,9 +2380,7 @@ export const create${names.singularPascal}UseCase = useCase
2300
2380
  .input(Create${names.singularPascal}InputSchema)
2301
2381
  .output(${names.singularPascal}Schema)
2302
2382
  .run(async ({ ctx, input }) => {
2303
- ${options.tenant ? `\t\tconst tenantId = ctx.tenant?.id;\n\t\tif (!tenantId) {\n\t\t\tthrow new Error("Tenant is required to create ${names.singularKebab}.");\n\t\t}\n\n` : ""}${options.auth ? `\t\tawait ctx.gate.authorize("${names.pluralCamel}.create");\n\n` : ""} const ${names.singularCamel} = await ctx.ports.${names.pluralCamel}.create({
2304
- ...input,
2305
- ${options.tenant ? `\t\t\ttenantId,\n` : ""} });
2383
+ ${options.tenant ? `\t\tconst scope = requireTenantScope(ctx);\n\n` : ""}${options.auth ? `\t\tawait ctx.gate.authorize("${names.pluralCamel}.create");\n\n` : ""} const ${names.singularCamel} = await ctx.ports.${names.pluralCamel}.create(input${options.tenant ? ", scope" : ""});
2306
2384
  ${options.events ? `\n\t\tawait ctx.ports.eventBus.publish(${names.singularPascal}Created, {\n\t\t\tid: ${names.singularCamel}.id,\n\t\t});\n` : ""}
2307
2385
  return ${names.singularCamel};
2308
2386
  });
@@ -2311,7 +2389,7 @@ ${options.events ? `\n\t\tawait ctx.ports.eventBus.publish(${names.singularPasca
2311
2389
  function getUseCaseFile(names, config, options) {
2312
2390
  const filePath = path.join(resourceUseCaseDir(names, config), `get-${names.singularKebab}.ts`);
2313
2391
  return `import "@beignet/core/server-only";
2314
- import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
2392
+ ${options.tenant ? `import { requireTenantScope } from "@beignet/core/tenancy";\n` : ""}import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
2315
2393
  import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
2316
2394
  import {
2317
2395
  ${names.singularPascal}IdInputSchema,
@@ -2323,7 +2401,7 @@ export const get${names.singularPascal}UseCase = useCase
2323
2401
  .input(${names.singularPascal}IdInputSchema)
2324
2402
  .output(${names.singularPascal}Schema)
2325
2403
  .run(async ({ ctx, input }) => {
2326
- ${options.tenant ? `\t\tconst tenantId = ctx.tenant?.id;\n\t\tif (!tenantId) {\n\t\t\tthrow new Error("Tenant is required to get ${names.singularKebab}.");\n\t\t}\n\n` : ""} const ${names.singularCamel} = await ctx.ports.${names.pluralCamel}.findById(input.id${options.tenant ? ", { tenantId }" : ""});
2404
+ ${options.tenant ? `\t\tconst scope = requireTenantScope(ctx);\n\n` : ""} const ${names.singularCamel} = await ctx.ports.${names.pluralCamel}.findById(input.id${options.tenant ? ", scope" : ""});
2327
2405
  if (!${names.singularCamel}) {
2328
2406
  throw appError("${names.singularPascal}NotFound", {
2329
2407
  details: { id: input.id },
@@ -2337,7 +2415,7 @@ ${options.tenant ? `\t\tconst tenantId = ctx.tenant?.id;\n\t\tif (!tenantId) {\n
2337
2415
  function updateUseCaseFile(names, config, options) {
2338
2416
  const filePath = path.join(resourceUseCaseDir(names, config), `update-${names.singularKebab}.ts`);
2339
2417
  return `import "@beignet/core/server-only";
2340
- import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
2418
+ ${options.tenant ? `import { requireTenantScope } from "@beignet/core/tenancy";\n` : ""}import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
2341
2419
  import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
2342
2420
  ${options.events ? `import { ${names.singularPascal}Updated } from "../domain/events";\n` : ""}import {
2343
2421
  Update${names.singularPascal}InputSchema,
@@ -2349,7 +2427,7 @@ export const update${names.singularPascal}UseCase = useCase
2349
2427
  .input(Update${names.singularPascal}InputSchema)
2350
2428
  .output(${names.singularPascal}Schema)
2351
2429
  .run(async ({ ctx, input }) => {
2352
- ${options.tenant ? `\t\tconst tenantId = ctx.tenant?.id;\n\t\tif (!tenantId) {\n\t\t\tthrow new Error("Tenant is required to update ${names.singularKebab}.");\n\t\t}\n\n` : ""} const existing = await ctx.ports.${names.pluralCamel}.findById(input.id${options.tenant ? ", { tenantId }" : ""});
2430
+ ${options.tenant ? `\t\tconst scope = requireTenantScope(ctx);\n\n` : ""} const existing = await ctx.ports.${names.pluralCamel}.findById(input.id${options.tenant ? ", scope" : ""});
2353
2431
  if (!existing) {
2354
2432
  throw appError("${names.singularPascal}NotFound", {
2355
2433
  details: { id: input.id },
@@ -2363,7 +2441,7 @@ ${options.auth ? `\t\tawait ctx.gate.authorize("${names.pluralCamel}.update", ex
2363
2441
 
2364
2442
  const ${names.singularCamel} = await ctx.ports.${names.pluralCamel}.update({
2365
2443
  ...input,
2366
- ${options.tenant ? `\t\t\ttenantId,\n` : ""} });
2444
+ }${options.tenant ? ", scope" : ""});
2367
2445
  if (!${names.singularCamel}) {
2368
2446
  throw appError("${names.singularPascal}Conflict", {
2369
2447
  details: { id: input.id, expectedVersion: existing.version },
@@ -2378,7 +2456,7 @@ function deleteUseCaseFile(names, config, options) {
2378
2456
  const filePath = path.join(resourceUseCaseDir(names, config), `delete-${names.singularKebab}.ts`);
2379
2457
  return `import "@beignet/core/server-only";
2380
2458
  import { z } from "zod";
2381
- import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
2459
+ ${options.tenant ? `import { requireTenantScope } from "@beignet/core/tenancy";\n` : ""}import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
2382
2460
  import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
2383
2461
  ${options.events ? `import { ${names.singularPascal}Deleted } from "../domain/events";\n` : ""}import { ${names.singularPascal}IdInputSchema } from "../schemas";
2384
2462
 
@@ -2387,7 +2465,7 @@ export const delete${names.singularPascal}UseCase = useCase
2387
2465
  .input(${names.singularPascal}IdInputSchema)
2388
2466
  .output(z.void())
2389
2467
  .run(async ({ ctx, input }) => {
2390
- ${options.tenant ? `\t\tconst tenantId = ctx.tenant?.id;\n\t\tif (!tenantId) {\n\t\t\tthrow new Error("Tenant is required to delete ${names.singularKebab}.");\n\t\t}\n\n` : ""}${options.auth ? `\t\tconst existing = await ctx.ports.${names.pluralCamel}.findById(input.id${options.tenant ? ", { tenantId }" : ""});\n\t\tif (!existing) {\n\t\t\tthrow appError("${names.singularPascal}NotFound", {\n\t\t\t\tdetails: { id: input.id },\n\t\t\t});\n\t\t}\n\t\tawait ctx.gate.authorize("${names.pluralCamel}.delete", existing);\n\n` : ""} const deleted = await ctx.ports.${names.pluralCamel}.delete(input.id${options.tenant ? ", { tenantId }" : ""});
2468
+ ${options.tenant ? `\t\tconst scope = requireTenantScope(ctx);\n\n` : ""}${options.auth ? `\t\tconst existing = await ctx.ports.${names.pluralCamel}.findById(input.id${options.tenant ? ", scope" : ""});\n\t\tif (!existing) {\n\t\t\tthrow appError("${names.singularPascal}NotFound", {\n\t\t\t\tdetails: { id: input.id },\n\t\t\t});\n\t\t}\n\t\tawait ctx.gate.authorize("${names.pluralCamel}.delete", existing);\n\n` : ""} const deleted = await ctx.ports.${names.pluralCamel}.delete(input.id${options.tenant ? ", scope" : ""});
2391
2469
  if (!deleted) {
2392
2470
  throw appError("${names.singularPascal}NotFound", {
2393
2471
  details: { id: input.id },
@@ -2417,7 +2495,7 @@ function repositoryPortFile(names, config, mode, options) {
2417
2495
  PageResult,
2418
2496
  SortDirection,
2419
2497
  } from "@beignet/core/pagination";
2420
- import type {
2498
+ ${options.tenant ? `import type { TenantScope } from "@beignet/core/tenancy";\n` : ""}import type {
2421
2499
  Create${names.singularPascal}Input,
2422
2500
  ${names.singularPascal}Cursor,
2423
2501
  ${names.singularPascal}SortBy,
@@ -2431,13 +2509,12 @@ export type List${names.pluralPascal}Query = {
2431
2509
  name?: string;
2432
2510
  sortBy: ${names.singularPascal}SortBy;
2433
2511
  sortDirection: SortDirection;
2434
- ${options.tenant ? `\ttenantId: string;\n` : ""}};
2435
- ${options.tenant ? `export type ${names.singularPascal}TenantFilter = { tenantId: string };\nexport type Create${names.singularPascal}RepositoryInput = Create${names.singularPascal}Input & ${names.singularPascal}TenantFilter;\n${mode === "resource" ? `export type Update${names.singularPascal}RepositoryInput = Update${names.singularPascal}Input & ${names.singularPascal}TenantFilter;\n` : ""}` : ""}
2512
+ };
2436
2513
 
2437
2514
  export interface ${names.singularPascal}Repository {
2438
- list(query: List${names.pluralPascal}Query): Promise<List${names.pluralPascal}Result>;
2439
- create(input: ${options.tenant ? `Create${names.singularPascal}RepositoryInput` : `Create${names.singularPascal}Input`}): Promise<${names.singularPascal}>;
2440
- ${mode === "resource" ? ` findById(id: string${options.tenant ? `, filter: ${names.singularPascal}TenantFilter` : ""}): Promise<${names.singularPascal} | null>;\n update(input: ${options.tenant ? `Update${names.singularPascal}RepositoryInput` : `Update${names.singularPascal}Input`}): Promise<${names.singularPascal} | null>;\n delete(id: string${options.tenant ? `, filter: ${names.singularPascal}TenantFilter` : ""}): Promise<boolean>;\n` : ""}}
2515
+ list(query: List${names.pluralPascal}Query${options.tenant ? ", scope: TenantScope" : ""}): Promise<List${names.pluralPascal}Result>;
2516
+ create(input: Create${names.singularPascal}Input${options.tenant ? ", scope: TenantScope" : ""}): Promise<${names.singularPascal}>;
2517
+ ${mode === "resource" ? ` findById(id: string${options.tenant ? ", scope: TenantScope" : ""}): Promise<${names.singularPascal} | null>;\n update(input: Update${names.singularPascal}Input${options.tenant ? ", scope: TenantScope" : ""}): Promise<${names.singularPascal} | null>;\n delete(id: string${options.tenant ? ", scope: TenantScope" : ""}): Promise<boolean>;\n` : ""}}
2441
2518
  `;
2442
2519
  }
2443
2520
  function inMemoryRepositoryFile(names, config, mode, options) {
@@ -2465,13 +2542,13 @@ function inMemoryRepositoryFile(names, config, mode, options) {
2465
2542
  ? " || existing.deletedAt !== null"
2466
2543
  : "";
2467
2544
  const deleteImplementation = options.softDelete
2468
- ? `${options.tenant ? `\t\t\tconst existing = ${names.pluralCamel}.get(id);\n\t\t\tif (!existing || existing.tenantId !== filter.tenantId || existing.deletedAt !== null) return false;\n` : `\t\t\tconst existing = ${names.pluralCamel}.get(id);\n\t\t\tif (!existing || existing.deletedAt !== null) return false;\n`} const now = new Date().toISOString();
2545
+ ? `${options.tenant ? `\t\t\tconst existing = ${names.pluralCamel}.get(id);\n\t\t\tif (!existing || existing.tenantId !== tenantId || existing.deletedAt !== null) return false;\n` : `\t\t\tconst existing = ${names.pluralCamel}.get(id);\n\t\t\tif (!existing || existing.deletedAt !== null) return false;\n`} const now = new Date().toISOString();
2469
2546
  ${names.pluralCamel}.set(id, { ...existing, deletedAt: now, updatedAt: now });
2470
2547
  return true;`
2471
- : `${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);`;
2548
+ : `${options.tenant ? `\t\t\tconst existing = ${names.pluralCamel}.get(id);\n\t\t\tif (existing?.tenantId !== tenantId) return false;\n` : ""} return ${names.pluralCamel}.delete(id);`;
2472
2549
  return `import "@beignet/core/server-only";
2473
2550
  import { cursorPageResult } from "@beignet/core/pagination";
2474
- import type { ${names.singularPascal}Repository } from "${aliasModule(repositoryPortPath)}";
2551
+ ${options.tenant ? `import { tenantScopeId } from "@beignet/core/tenancy";\n` : ""}import type { ${names.singularPascal}Repository } from "${aliasModule(repositoryPortPath)}";
2475
2552
  import { encode${names.singularPascal}Cursor } from "${aliasModule(resourceSchemaFilePath(names, config))}";
2476
2553
  import type {
2477
2554
  Create${names.singularPascal}Input,
@@ -2539,10 +2616,10 @@ export function createInMemory${names.singularPascal}Repository(
2539
2616
  );
2540
2617
 
2541
2618
  return {
2542
- async list(query) {
2619
+ async list(query${options.tenant ? ", scope" : ""}) {
2543
2620
  const name = query.name?.toLocaleLowerCase();
2544
- const all${names.pluralPascal} = Array.from(${names.pluralCamel}.values())
2545
- ${activeFilter}${options.tenant ? `\t\t\t\t.filter((${names.singularCamel}) => ${names.singularCamel}.tenantId === query.tenantId)\n` : ""} .filter((${names.singularCamel}) => !name || ${names.singularCamel}.name.toLocaleLowerCase().includes(name))
2621
+ ${options.tenant ? `\t\t\tconst tenantId = tenantScopeId(scope);\n` : ""} const all${names.pluralPascal} = Array.from(${names.pluralCamel}.values())
2622
+ ${activeFilter}${options.tenant ? `\t\t\t\t.filter((${names.singularCamel}) => ${names.singularCamel}.tenantId === tenantId)\n` : ""} .filter((${names.singularCamel}) => !name || ${names.singularCamel}.name.toLocaleLowerCase().includes(name))
2546
2623
  .sort((left, right) => compare${names.pluralPascal}(left, right, query))
2547
2624
  .filter((${names.singularCamel}) => isAfter${names.singularPascal}Cursor(${names.singularCamel}, query));
2548
2625
  const pageItems = all${names.pluralPascal}.slice(0, query.page.limit);
@@ -2557,11 +2634,11 @@ ${activeFilter}${options.tenant ? `\t\t\t\t.filter((${names.singularCamel}) => $
2557
2634
  nextCursor,
2558
2635
  );
2559
2636
  },
2560
- async create(input) {
2637
+ async create(input${options.tenant ? ", scope" : ""}) {
2561
2638
  const now = new Date().toISOString();
2562
- const ${names.singularCamel}: ${names.singularPascal} = {
2639
+ ${options.tenant ? `\t\t\tconst tenantId = tenantScopeId(scope);\n` : ""} const ${names.singularCamel}: ${names.singularPascal} = {
2563
2640
  id: crypto.randomUUID(),
2564
- ${options.tenant ? `\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input.name,
2641
+ ${options.tenant ? `\t\t\t\ttenantId,\n` : ""} name: input.name,
2565
2642
  version: 1,
2566
2643
  createdAt: now,
2567
2644
  updatedAt: now,
@@ -2571,7 +2648,7 @@ ${options.tenant ? `\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input.na
2571
2648
  ${newDeletedAtField} });
2572
2649
  return ${names.singularCamel};
2573
2650
  },
2574
- ${mode === "resource" ? ` async findById(id: string${options.tenant ? ", filter" : ""}) {\n const ${names.singularCamel} = ${names.pluralCamel}.get(id) ?? null;\n${options.tenant ? `\t\t\tif (${names.singularCamel}?.tenantId !== filter.tenantId) return null;\n` : ""}${findDeletedGuard} return ${names.singularCamel} ? to${names.singularPascal}(${names.singularCamel}) : null;\n },\n async update(input) {\n const existing = ${names.pluralCamel}.get(input.id);\n if (!existing${options.tenant ? " || existing.tenantId !== input.tenantId" : ""}${updateDeletedGuard} || existing.version !== input.version) return null;\n\n const next: ${mapValueType} = {\n ...existing,\n name: input.name,\n version: existing.version + 1,\n updatedAt: new Date().toISOString(),\n };\n ${names.pluralCamel}.set(input.id, next);\n return to${names.singularPascal}(next);\n },\n async delete(id: string${options.tenant ? ", filter" : ""}) {\n${deleteImplementation}\n },\n` : ""}
2651
+ ${mode === "resource" ? ` async findById(id: string${options.tenant ? ", scope" : ""}) {\n${options.tenant ? `\t\t\tconst tenantId = tenantScopeId(scope);\n` : ""} const ${names.singularCamel} = ${names.pluralCamel}.get(id) ?? null;\n${options.tenant ? `\t\t\tif (${names.singularCamel}?.tenantId !== tenantId) return null;\n` : ""}${findDeletedGuard} return ${names.singularCamel} ? to${names.singularPascal}(${names.singularCamel}) : null;\n },\n async update(input${options.tenant ? ", scope" : ""}) {\n${options.tenant ? `\t\t\tconst tenantId = tenantScopeId(scope);\n` : ""} const existing = ${names.pluralCamel}.get(input.id);\n if (!existing${options.tenant ? " || existing.tenantId !== tenantId" : ""}${updateDeletedGuard} || existing.version !== input.version) return null;\n\n const next: ${mapValueType} = {\n ...existing,\n name: input.name,\n version: existing.version + 1,\n updatedAt: new Date().toISOString(),\n };\n ${names.pluralCamel}.set(input.id, next);\n return to${names.singularPascal}(next);\n },\n async delete(id: string${options.tenant ? ", scope" : ""}) {\n${options.tenant ? `\t\t\tconst tenantId = tenantScopeId(scope);\n` : ""}${deleteImplementation}\n },\n` : ""}
2575
2652
  };
2576
2653
  }
2577
2654
  `;
@@ -2677,20 +2754,20 @@ function drizzleRepositoryFile(names, config, mode, options, dialect) {
2677
2754
  const findWhere = drizzleResourceWhere(names, options, [
2678
2755
  `eq(schema.${names.pluralCamel}.id, id)`,
2679
2756
  ...(options.tenant
2680
- ? [`eq(schema.${names.pluralCamel}.tenantId, filter.tenantId)`]
2757
+ ? [`eq(schema.${names.pluralCamel}.tenantId, tenantScopeId(scope))`]
2681
2758
  : []),
2682
2759
  ]);
2683
2760
  const updateWhere = drizzleResourceWhere(names, options, [
2684
2761
  `eq(schema.${names.pluralCamel}.id, input.id)`,
2685
2762
  `eq(schema.${names.pluralCamel}.version, input.version)`,
2686
2763
  ...(options.tenant
2687
- ? [`eq(schema.${names.pluralCamel}.tenantId, input.tenantId)`]
2764
+ ? [`eq(schema.${names.pluralCamel}.tenantId, tenantScopeId(scope))`]
2688
2765
  : []),
2689
2766
  ]);
2690
2767
  const deleteWhere = drizzleResourceWhere(names, options, [
2691
2768
  `eq(schema.${names.pluralCamel}.id, id)`,
2692
2769
  ...(options.tenant
2693
- ? [`eq(schema.${names.pluralCamel}.tenantId, filter.tenantId)`]
2770
+ ? [`eq(schema.${names.pluralCamel}.tenantId, tenantScopeId(scope))`]
2694
2771
  : []),
2695
2772
  ]);
2696
2773
  const drizzleImports = [
@@ -2708,7 +2785,7 @@ function drizzleRepositoryFile(names, config, mode, options, dialect) {
2708
2785
  // After a version-guarded update succeeds, re-select by identity only —
2709
2786
  // matching what `.returning()` yields on the dialects that support it.
2710
2787
  const updateReselectWhere = options.tenant
2711
- ? `and(eq(schema.${names.pluralCamel}.id, input.id), eq(schema.${names.pluralCamel}.tenantId, input.tenantId))`
2788
+ ? `and(eq(schema.${names.pluralCamel}.id, input.id), eq(schema.${names.pluralCamel}.tenantId, tenantScopeId(scope)))`
2712
2789
  : `eq(schema.${names.pluralCamel}.id, input.id)`;
2713
2790
  // drizzle-orm/mysql2 has no `.returning(...)`; mutations resolve to a
2714
2791
  // `[ResultSetHeader, FieldPacket[]]` tuple typed as `unknown` through the
@@ -2729,13 +2806,13 @@ function affectedRows(result: unknown): number {
2729
2806
  `
2730
2807
  : "";
2731
2808
  const createMethod = dialect.supportsReturning
2732
- ? ` async create(input) {
2809
+ ? ` async create(input${options.tenant ? ", scope" : ""}) {
2733
2810
  const now = new Date().toISOString();
2734
2811
  const [row] = await db
2735
2812
  .insert(schema.${names.pluralCamel})
2736
2813
  .values({
2737
2814
  id: crypto.randomUUID(),
2738
- ${options.tenant ? `\t\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input.name,
2815
+ ${options.tenant ? `\t\t\t\t\ttenantId: tenantScopeId(scope),\n` : ""} name: input.name,
2739
2816
  version: 1,
2740
2817
  createdAt: now,
2741
2818
  updatedAt: now,
@@ -2749,11 +2826,11 @@ ${options.tenant ? `\t\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input
2749
2826
  return to${names.singularPascal}(row);
2750
2827
  },
2751
2828
  `
2752
- : ` async create(input) {
2829
+ : ` async create(input${options.tenant ? ", scope" : ""}) {
2753
2830
  const now = new Date().toISOString();
2754
2831
  const ${names.singularCamel}: ${names.singularPascal} = {
2755
2832
  id: crypto.randomUUID(),
2756
- ${options.tenant ? `\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input.name,
2833
+ ${options.tenant ? `\t\t\t\ttenantId: tenantScopeId(scope),\n` : ""} name: input.name,
2757
2834
  version: 1,
2758
2835
  createdAt: now,
2759
2836
  updatedAt: now,
@@ -2766,7 +2843,7 @@ ${options.tenant ? `\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input.na
2766
2843
  const resourceMethods = mode !== "resource"
2767
2844
  ? ""
2768
2845
  : dialect.supportsReturning
2769
- ? ` async findById(id: string${options.tenant ? ", filter" : ""}) {
2846
+ ? ` async findById(id: string${options.tenant ? ", scope" : ""}) {
2770
2847
  const [row] = await db
2771
2848
  .select()
2772
2849
  .from(schema.${names.pluralCamel})
@@ -2775,7 +2852,7 @@ ${options.tenant ? `\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input.na
2775
2852
 
2776
2853
  return row ? to${names.singularPascal}(row) : null;
2777
2854
  },
2778
- async update(input) {
2855
+ async update(input${options.tenant ? ", scope" : ""}) {
2779
2856
  const [row] = await db
2780
2857
  .update(schema.${names.pluralCamel})
2781
2858
  .set({
@@ -2788,12 +2865,12 @@ ${options.tenant ? `\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input.na
2788
2865
 
2789
2866
  return row ? to${names.singularPascal}(row) : null;
2790
2867
  },
2791
- async delete(id: string${options.tenant ? ", filter" : ""}) {
2868
+ async delete(id: string${options.tenant ? ", scope" : ""}) {
2792
2869
  ${options.softDelete ? `\t\t\tconst now = new Date().toISOString();\n\t\t\tconst rows = await db\n\t\t\t\t.update(schema.${names.pluralCamel})\n\t\t\t\t.set({ deletedAt: now, updatedAt: now })\n\t\t\t\t.where(${deleteWhere})\n\t\t\t\t.returning({ id: schema.${names.pluralCamel}.id });\n` : `\t\t\tconst rows = await db\n\t\t\t\t.delete(schema.${names.pluralCamel})\n\t\t\t\t.where(${deleteWhere})\n\t\t\t\t.returning({ id: schema.${names.pluralCamel}.id });\n`}
2793
2870
  return rows.length > 0;
2794
2871
  },
2795
2872
  `
2796
- : ` async findById(id: string${options.tenant ? ", filter" : ""}) {
2873
+ : ` async findById(id: string${options.tenant ? ", scope" : ""}) {
2797
2874
  const [row] = await db
2798
2875
  .select()
2799
2876
  .from(schema.${names.pluralCamel})
@@ -2802,7 +2879,7 @@ ${options.softDelete ? `\t\t\tconst now = new Date().toISOString();\n\t\t\tconst
2802
2879
 
2803
2880
  return row ? to${names.singularPascal}(row) : null;
2804
2881
  },
2805
- async update(input) {
2882
+ async update(input${options.tenant ? ", scope" : ""}) {
2806
2883
  const result = await db
2807
2884
  .update(schema.${names.pluralCamel})
2808
2885
  .set({
@@ -2822,14 +2899,14 @@ ${options.softDelete ? `\t\t\tconst now = new Date().toISOString();\n\t\t\tconst
2822
2899
 
2823
2900
  return row ? to${names.singularPascal}(row) : null;
2824
2901
  },
2825
- async delete(id: string${options.tenant ? ", filter" : ""}) {
2902
+ async delete(id: string${options.tenant ? ", scope" : ""}) {
2826
2903
  ${options.softDelete ? `\t\t\tconst now = new Date().toISOString();\n\t\t\tconst result = await db\n\t\t\t\t.update(schema.${names.pluralCamel})\n\t\t\t\t.set({ deletedAt: now, updatedAt: now })\n\t\t\t\t.where(${deleteWhere});\n` : `\t\t\tconst result = await db\n\t\t\t\t.delete(schema.${names.pluralCamel})\n\t\t\t\t.where(${deleteWhere});\n`}
2827
2904
  return affectedRows(result) > 0;
2828
2905
  },
2829
2906
  `;
2830
2907
  return `import "@beignet/core/server-only";
2831
2908
  import { cursorPageResult } from "@beignet/core/pagination";
2832
- import type { ${dialect.dbTypeName} } from "@beignet/provider-db-drizzle/${dialect.subpath}";
2909
+ ${options.tenant ? `import { tenantScopeId } from "@beignet/core/tenancy";\n` : ""}import type { ${dialect.dbTypeName} } from "@beignet/provider-db-drizzle/${dialect.subpath}";
2833
2910
  import { ${drizzleImports.join(", ")} } from "drizzle-orm";
2834
2911
  import type { ${names.singularPascal}Repository } from "${aliasModule(repositoryPortPath)}";
2835
2912
  import { encode${names.singularPascal}Cursor } from "${aliasModule(resourceSchemaFilePath(names, config))}";
@@ -2876,9 +2953,9 @@ function ${names.singularCamel}CursorFilter(
2876
2953
 
2877
2954
  function ${names.singularCamel}ListWhere(
2878
2955
  query: List${names.pluralPascal}Query,
2879
- ): SQL<unknown> | undefined {
2956
+ ${options.tenant ? `\tscope: Parameters<${names.singularPascal}Repository["list"]>[1],\n` : ""}): SQL<unknown> | undefined {
2880
2957
  const filters: SQL<unknown>[] = [
2881
- ${options.tenant ? `\t\teq(schema.${names.pluralCamel}.tenantId, query.tenantId),\n` : ""}${options.softDelete ? `\t\tisNull(schema.${names.pluralCamel}.deletedAt),\n` : ""} ];
2958
+ ${options.tenant ? `\t\teq(schema.${names.pluralCamel}.tenantId, tenantScopeId(scope)),\n` : ""}${options.softDelete ? `\t\tisNull(schema.${names.pluralCamel}.deletedAt),\n` : ""} ];
2882
2959
  const cursor = ${names.singularCamel}CursorFilter(query);
2883
2960
 
2884
2961
  if (query.name) {
@@ -2912,8 +2989,8 @@ export function createDrizzle${names.singularPascal}Repository(
2912
2989
  db: ${dialect.dbTypeName}<typeof schema>,
2913
2990
  ): ${names.singularPascal}Repository {
2914
2991
  return {
2915
- async list(query) {
2916
- const where = ${names.singularCamel}ListWhere(query);
2992
+ async list(query${options.tenant ? ", scope" : ""}) {
2993
+ const where = ${names.singularCamel}ListWhere(query${options.tenant ? ", scope" : ""});
2917
2994
  const rows = await db
2918
2995
  .select()
2919
2996
  .from(schema.${names.pluralCamel})
@@ -3074,7 +3151,7 @@ export const ${names.exportName} = useCase
3074
3151
  }
3075
3152
  function useCaseTestFile(names, config) {
3076
3153
  const filePath = useCaseTestFilePath(names, config);
3077
- return `import { describe, expect, it } from "bun:test";
3154
+ return `import { describe, expect, it } from "@/lib/beignet-test";
3078
3155
  import { createUseCaseTester } from "@beignet/core/application";
3079
3156
  import { createInMemoryDevtools } from "@beignet/devtools";
3080
3157
  import { createTestContextFactory, createTestPorts } from "@beignet/core/testing";
@@ -3232,7 +3309,7 @@ export const ${names.singularCamel}Events = [
3232
3309
  `;
3233
3310
  }
3234
3311
  function resourcePolicyTestFile(names, options) {
3235
- return `import { describe, expect, it } from "bun:test";
3312
+ return `import { describe, expect, it } from "@/lib/beignet-test";
3236
3313
  import { createPolicyTester } from "@beignet/core/ports/testing";
3237
3314
  import { ${names.singularCamel}Policy } from "../policy";
3238
3315
  import type { ${names.singularPascal} } from "../schemas";
@@ -3459,9 +3536,8 @@ ${names.timezone ? `\t\ttimezone: "${names.timezone}",\n` : ""}\t\tpayload: ${na
3459
3536
  `;
3460
3537
  }
3461
3538
  function uploadFile(names, config) {
3462
- return `import { defineUpload } from "@beignet/core/uploads";
3463
- import { z } from "zod";
3464
- import type { AppContext } from "${aliasModule(config.paths.appContext)}";
3539
+ return `import { z } from "zod";
3540
+ import { defineUpload } from "${aliasModule(config.paths.uploadsBuilder)}";
3465
3541
 
3466
3542
  export const ${names.metadataSchemaName} = z.object({
3467
3543
  \tresourceId: z.string().min(1),
@@ -3469,12 +3545,7 @@ export const ${names.metadataSchemaName} = z.object({
3469
3545
 
3470
3546
  export type ${names.metadataTypeName} = z.infer<typeof ${names.metadataSchemaName}>;
3471
3547
 
3472
- export const ${names.uploadExportName} = defineUpload<
3473
- \t"${names.uploadName}",
3474
- \ttypeof ${names.metadataSchemaName},
3475
- \tAppContext,
3476
- \t{ objectKeys: string[] }
3477
- >("${names.uploadName}", {
3548
+ export const ${names.uploadExportName} = defineUpload("${names.uploadName}", {
3478
3549
  \tmetadata: ${names.metadataSchemaName},
3479
3550
  \tfile: {
3480
3551
  \t\tcontentTypes: ["application/pdf", "text/plain"],
@@ -3765,7 +3836,7 @@ function testFile(names, config, mode, options) {
3765
3836
  const repositoryPath = path.join(path.dirname(config.paths.infrastructurePorts), names.pluralKebab, `in-memory-${names.singularKebab}-repository.ts`);
3766
3837
  const serverContextPath = path.join(path.dirname(config.paths.server), "context.ts");
3767
3838
  const requestTarget = "app";
3768
- return `import { describe, expect, it } from "bun:test";
3839
+ return `import { describe, expect, it } from "@/lib/beignet-test";
3769
3840
  import { createUseCaseTester } from "@beignet/core/application";
3770
3841
  ${mode === "resource" ? `import { isAppError } from "@beignet/core/errors";\n` : ""}import { defineRoutes } from "@beignet/web";
3771
3842
  import { createTestApp } from "@beignet/web/testing";