@beignet/cli 0.0.32 → 0.0.34

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 (92) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +29 -19
  3. package/dist/choices.d.ts +3 -3
  4. package/dist/choices.d.ts.map +1 -1
  5. package/dist/choices.js +2 -0
  6. package/dist/choices.js.map +1 -1
  7. package/dist/config.d.ts +1 -0
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/config.js +1 -0
  10. package/dist/config.js.map +1 -1
  11. package/dist/db.js +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +279 -1
  14. package/dist/index.js.map +1 -1
  15. package/dist/inspect.d.ts.map +1 -1
  16. package/dist/inspect.js +800 -60
  17. package/dist/inspect.js.map +1 -1
  18. package/dist/make/inbox.d.ts.map +1 -1
  19. package/dist/make/inbox.js +8 -6
  20. package/dist/make/inbox.js.map +1 -1
  21. package/dist/make/payments.d.ts.map +1 -1
  22. package/dist/make/payments.js +61 -32
  23. package/dist/make/payments.js.map +1 -1
  24. package/dist/make/shared.d.ts +11 -2
  25. package/dist/make/shared.d.ts.map +1 -1
  26. package/dist/make/shared.js +45 -11
  27. package/dist/make/shared.js.map +1 -1
  28. package/dist/make/tenancy.d.ts.map +1 -1
  29. package/dist/make/tenancy.js +9 -7
  30. package/dist/make/tenancy.js.map +1 -1
  31. package/dist/make.d.ts.map +1 -1
  32. package/dist/make.js +134 -60
  33. package/dist/make.js.map +1 -1
  34. package/dist/outbox.d.ts +114 -1
  35. package/dist/outbox.d.ts.map +1 -1
  36. package/dist/outbox.js +190 -0
  37. package/dist/outbox.js.map +1 -1
  38. package/dist/preflight.js +1 -1
  39. package/dist/preflight.js.map +1 -1
  40. package/dist/provider-add.d.ts.map +1 -1
  41. package/dist/provider-add.js +91 -62
  42. package/dist/provider-add.js.map +1 -1
  43. package/dist/registry-edits.js +2 -1
  44. package/dist/registry-edits.js.map +1 -1
  45. package/dist/task.d.ts.map +1 -1
  46. package/dist/task.js +2 -3
  47. package/dist/task.js.map +1 -1
  48. package/dist/templates/agents.d.ts.map +1 -1
  49. package/dist/templates/agents.js +6 -3
  50. package/dist/templates/agents.js.map +1 -1
  51. package/dist/templates/base.js +6 -6
  52. package/dist/templates/base.js.map +1 -1
  53. package/dist/templates/index.d.ts.map +1 -1
  54. package/dist/templates/index.js +2 -0
  55. package/dist/templates/index.js.map +1 -1
  56. package/dist/templates/server.d.ts.map +1 -1
  57. package/dist/templates/server.js +17 -10
  58. package/dist/templates/server.js.map +1 -1
  59. package/dist/templates/shared.d.ts +3 -1
  60. package/dist/templates/shared.d.ts.map +1 -1
  61. package/dist/templates/shared.js +11 -1
  62. package/dist/templates/shared.js.map +1 -1
  63. package/dist/templates/testing.d.ts +5 -0
  64. package/dist/templates/testing.d.ts.map +1 -0
  65. package/dist/templates/testing.js +542 -0
  66. package/dist/templates/testing.js.map +1 -0
  67. package/dist/templates/todos.js +4 -4
  68. package/package.json +3 -2
  69. package/skills/app-structure/SKILL.md +14 -4
  70. package/src/choices.ts +3 -0
  71. package/src/config.ts +2 -0
  72. package/src/db.ts +1 -1
  73. package/src/index.ts +437 -1
  74. package/src/inspect.ts +1248 -156
  75. package/src/make/inbox.ts +8 -6
  76. package/src/make/payments.ts +61 -32
  77. package/src/make/shared.ts +79 -12
  78. package/src/make/tenancy.ts +9 -7
  79. package/src/make.ts +177 -59
  80. package/src/outbox.ts +363 -0
  81. package/src/preflight.ts +1 -1
  82. package/src/provider-add.ts +92 -62
  83. package/src/registry-edits.ts +2 -1
  84. package/src/task.ts +2 -3
  85. package/src/templates/agents.ts +6 -3
  86. package/src/templates/base.ts +6 -6
  87. package/src/templates/index.ts +2 -0
  88. package/src/templates/server.ts +17 -10
  89. package/src/templates/shared.ts +13 -1
  90. package/src/templates/testing.ts +545 -0
  91. package/src/templates/todos.ts +4 -4
  92. 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;
@@ -1584,6 +1659,7 @@ function resourceFiles(names, config, persistence = "memory", mode = "feature",
1584
1659
  const infraDir = infrastructureDir(config);
1585
1660
  const featureDir = resourceFeatureDir(names, config);
1586
1661
  const files = [
1662
+ ...testSupportTemplateFiles,
1587
1663
  {
1588
1664
  path: resourceContractFilePath(names, config),
1589
1665
  content: contractFile(names, config, mode, options),
@@ -1709,6 +1785,7 @@ function useCaseFiles(names, config) {
1709
1785
  }
1710
1786
  function testFiles(names, config) {
1711
1787
  return [
1788
+ ...testSupportTemplateFiles,
1712
1789
  {
1713
1790
  path: useCaseTestFilePath(names, config),
1714
1791
  content: useCaseTestFile(names, config),
@@ -2261,7 +2338,7 @@ function listUseCaseFile(names, config, options) {
2261
2338
  const filePath = path.join(resourceUseCaseDir(names, config), `list-${names.pluralKebab}.ts`);
2262
2339
  return `import "@beignet/core/server-only";
2263
2340
  import { normalizeCursorPage } from "@beignet/core/pagination";
2264
- 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)}";
2265
2342
  import {
2266
2343
  decode${names.singularPascal}Cursor,
2267
2344
  List${names.pluralPascal}InputSchema,
@@ -2273,7 +2350,7 @@ export const list${names.pluralPascal}UseCase = useCase
2273
2350
  .input(List${names.pluralPascal}InputSchema)
2274
2351
  .output(List${names.pluralPascal}OutputSchema)
2275
2352
  .run(async ({ ctx, input }) => {
2276
- ${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, {
2277
2354
  defaultLimit: 20,
2278
2355
  maxLimit: 100,
2279
2356
  });
@@ -2284,14 +2361,14 @@ ${options.tenant ? `\t\tconst tenantId = ctx.tenant?.id;\n\t\tif (!tenantId) {\n
2284
2361
  name: input.name,
2285
2362
  sortBy: input.sortBy,
2286
2363
  sortDirection: input.sortDirection,
2287
- ${options.tenant ? `\t\t\ttenantId,\n` : ""} });
2364
+ }${options.tenant ? ", scope" : ""});
2288
2365
  });
2289
2366
  `;
2290
2367
  }
2291
2368
  function createUseCaseFile(names, config, options) {
2292
2369
  const filePath = path.join(resourceUseCaseDir(names, config), `create-${names.singularKebab}.ts`);
2293
2370
  return `import "@beignet/core/server-only";
2294
- 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)}";
2295
2372
  import {
2296
2373
  Create${names.singularPascal}InputSchema,
2297
2374
  ${names.singularPascal}Schema,
@@ -2303,9 +2380,7 @@ export const create${names.singularPascal}UseCase = useCase
2303
2380
  .input(Create${names.singularPascal}InputSchema)
2304
2381
  .output(${names.singularPascal}Schema)
2305
2382
  .run(async ({ ctx, input }) => {
2306
- ${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({
2307
- ...input,
2308
- ${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" : ""});
2309
2384
  ${options.events ? `\n\t\tawait ctx.ports.eventBus.publish(${names.singularPascal}Created, {\n\t\t\tid: ${names.singularCamel}.id,\n\t\t});\n` : ""}
2310
2385
  return ${names.singularCamel};
2311
2386
  });
@@ -2314,7 +2389,7 @@ ${options.events ? `\n\t\tawait ctx.ports.eventBus.publish(${names.singularPasca
2314
2389
  function getUseCaseFile(names, config, options) {
2315
2390
  const filePath = path.join(resourceUseCaseDir(names, config), `get-${names.singularKebab}.ts`);
2316
2391
  return `import "@beignet/core/server-only";
2317
- import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
2392
+ ${options.tenant ? `import { requireTenantScope } from "@beignet/core/tenancy";\n` : ""}import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
2318
2393
  import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
2319
2394
  import {
2320
2395
  ${names.singularPascal}IdInputSchema,
@@ -2326,7 +2401,7 @@ export const get${names.singularPascal}UseCase = useCase
2326
2401
  .input(${names.singularPascal}IdInputSchema)
2327
2402
  .output(${names.singularPascal}Schema)
2328
2403
  .run(async ({ ctx, input }) => {
2329
- ${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" : ""});
2330
2405
  if (!${names.singularCamel}) {
2331
2406
  throw appError("${names.singularPascal}NotFound", {
2332
2407
  details: { id: input.id },
@@ -2340,7 +2415,7 @@ ${options.tenant ? `\t\tconst tenantId = ctx.tenant?.id;\n\t\tif (!tenantId) {\n
2340
2415
  function updateUseCaseFile(names, config, options) {
2341
2416
  const filePath = path.join(resourceUseCaseDir(names, config), `update-${names.singularKebab}.ts`);
2342
2417
  return `import "@beignet/core/server-only";
2343
- import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
2418
+ ${options.tenant ? `import { requireTenantScope } from "@beignet/core/tenancy";\n` : ""}import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
2344
2419
  import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
2345
2420
  ${options.events ? `import { ${names.singularPascal}Updated } from "../domain/events";\n` : ""}import {
2346
2421
  Update${names.singularPascal}InputSchema,
@@ -2352,7 +2427,7 @@ export const update${names.singularPascal}UseCase = useCase
2352
2427
  .input(Update${names.singularPascal}InputSchema)
2353
2428
  .output(${names.singularPascal}Schema)
2354
2429
  .run(async ({ ctx, input }) => {
2355
- ${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" : ""});
2356
2431
  if (!existing) {
2357
2432
  throw appError("${names.singularPascal}NotFound", {
2358
2433
  details: { id: input.id },
@@ -2366,7 +2441,7 @@ ${options.auth ? `\t\tawait ctx.gate.authorize("${names.pluralCamel}.update", ex
2366
2441
 
2367
2442
  const ${names.singularCamel} = await ctx.ports.${names.pluralCamel}.update({
2368
2443
  ...input,
2369
- ${options.tenant ? `\t\t\ttenantId,\n` : ""} });
2444
+ }${options.tenant ? ", scope" : ""});
2370
2445
  if (!${names.singularCamel}) {
2371
2446
  throw appError("${names.singularPascal}Conflict", {
2372
2447
  details: { id: input.id, expectedVersion: existing.version },
@@ -2381,7 +2456,7 @@ function deleteUseCaseFile(names, config, options) {
2381
2456
  const filePath = path.join(resourceUseCaseDir(names, config), `delete-${names.singularKebab}.ts`);
2382
2457
  return `import "@beignet/core/server-only";
2383
2458
  import { z } from "zod";
2384
- import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
2459
+ ${options.tenant ? `import { requireTenantScope } from "@beignet/core/tenancy";\n` : ""}import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
2385
2460
  import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
2386
2461
  ${options.events ? `import { ${names.singularPascal}Deleted } from "../domain/events";\n` : ""}import { ${names.singularPascal}IdInputSchema } from "../schemas";
2387
2462
 
@@ -2390,7 +2465,7 @@ export const delete${names.singularPascal}UseCase = useCase
2390
2465
  .input(${names.singularPascal}IdInputSchema)
2391
2466
  .output(z.void())
2392
2467
  .run(async ({ ctx, input }) => {
2393
- ${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" : ""});
2394
2469
  if (!deleted) {
2395
2470
  throw appError("${names.singularPascal}NotFound", {
2396
2471
  details: { id: input.id },
@@ -2420,7 +2495,7 @@ function repositoryPortFile(names, config, mode, options) {
2420
2495
  PageResult,
2421
2496
  SortDirection,
2422
2497
  } from "@beignet/core/pagination";
2423
- import type {
2498
+ ${options.tenant ? `import type { TenantScope } from "@beignet/core/tenancy";\n` : ""}import type {
2424
2499
  Create${names.singularPascal}Input,
2425
2500
  ${names.singularPascal}Cursor,
2426
2501
  ${names.singularPascal}SortBy,
@@ -2434,13 +2509,12 @@ export type List${names.pluralPascal}Query = {
2434
2509
  name?: string;
2435
2510
  sortBy: ${names.singularPascal}SortBy;
2436
2511
  sortDirection: SortDirection;
2437
- ${options.tenant ? `\ttenantId: string;\n` : ""}};
2438
- ${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
+ };
2439
2513
 
2440
2514
  export interface ${names.singularPascal}Repository {
2441
- list(query: List${names.pluralPascal}Query): Promise<List${names.pluralPascal}Result>;
2442
- create(input: ${options.tenant ? `Create${names.singularPascal}RepositoryInput` : `Create${names.singularPascal}Input`}): Promise<${names.singularPascal}>;
2443
- ${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` : ""}}
2444
2518
  `;
2445
2519
  }
2446
2520
  function inMemoryRepositoryFile(names, config, mode, options) {
@@ -2468,13 +2542,13 @@ function inMemoryRepositoryFile(names, config, mode, options) {
2468
2542
  ? " || existing.deletedAt !== null"
2469
2543
  : "";
2470
2544
  const deleteImplementation = options.softDelete
2471
- ? `${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();
2472
2546
  ${names.pluralCamel}.set(id, { ...existing, deletedAt: now, updatedAt: now });
2473
2547
  return true;`
2474
- : `${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);`;
2475
2549
  return `import "@beignet/core/server-only";
2476
2550
  import { cursorPageResult } from "@beignet/core/pagination";
2477
- 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)}";
2478
2552
  import { encode${names.singularPascal}Cursor } from "${aliasModule(resourceSchemaFilePath(names, config))}";
2479
2553
  import type {
2480
2554
  Create${names.singularPascal}Input,
@@ -2542,10 +2616,10 @@ export function createInMemory${names.singularPascal}Repository(
2542
2616
  );
2543
2617
 
2544
2618
  return {
2545
- async list(query) {
2619
+ async list(query${options.tenant ? ", scope" : ""}) {
2546
2620
  const name = query.name?.toLocaleLowerCase();
2547
- const all${names.pluralPascal} = Array.from(${names.pluralCamel}.values())
2548
- ${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))
2549
2623
  .sort((left, right) => compare${names.pluralPascal}(left, right, query))
2550
2624
  .filter((${names.singularCamel}) => isAfter${names.singularPascal}Cursor(${names.singularCamel}, query));
2551
2625
  const pageItems = all${names.pluralPascal}.slice(0, query.page.limit);
@@ -2560,11 +2634,11 @@ ${activeFilter}${options.tenant ? `\t\t\t\t.filter((${names.singularCamel}) => $
2560
2634
  nextCursor,
2561
2635
  );
2562
2636
  },
2563
- async create(input) {
2637
+ async create(input${options.tenant ? ", scope" : ""}) {
2564
2638
  const now = new Date().toISOString();
2565
- const ${names.singularCamel}: ${names.singularPascal} = {
2639
+ ${options.tenant ? `\t\t\tconst tenantId = tenantScopeId(scope);\n` : ""} const ${names.singularCamel}: ${names.singularPascal} = {
2566
2640
  id: crypto.randomUUID(),
2567
- ${options.tenant ? `\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input.name,
2641
+ ${options.tenant ? `\t\t\t\ttenantId,\n` : ""} name: input.name,
2568
2642
  version: 1,
2569
2643
  createdAt: now,
2570
2644
  updatedAt: now,
@@ -2574,7 +2648,7 @@ ${options.tenant ? `\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input.na
2574
2648
  ${newDeletedAtField} });
2575
2649
  return ${names.singularCamel};
2576
2650
  },
2577
- ${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` : ""}
2578
2652
  };
2579
2653
  }
2580
2654
  `;
@@ -2680,20 +2754,20 @@ function drizzleRepositoryFile(names, config, mode, options, dialect) {
2680
2754
  const findWhere = drizzleResourceWhere(names, options, [
2681
2755
  `eq(schema.${names.pluralCamel}.id, id)`,
2682
2756
  ...(options.tenant
2683
- ? [`eq(schema.${names.pluralCamel}.tenantId, filter.tenantId)`]
2757
+ ? [`eq(schema.${names.pluralCamel}.tenantId, tenantScopeId(scope))`]
2684
2758
  : []),
2685
2759
  ]);
2686
2760
  const updateWhere = drizzleResourceWhere(names, options, [
2687
2761
  `eq(schema.${names.pluralCamel}.id, input.id)`,
2688
2762
  `eq(schema.${names.pluralCamel}.version, input.version)`,
2689
2763
  ...(options.tenant
2690
- ? [`eq(schema.${names.pluralCamel}.tenantId, input.tenantId)`]
2764
+ ? [`eq(schema.${names.pluralCamel}.tenantId, tenantScopeId(scope))`]
2691
2765
  : []),
2692
2766
  ]);
2693
2767
  const deleteWhere = drizzleResourceWhere(names, options, [
2694
2768
  `eq(schema.${names.pluralCamel}.id, id)`,
2695
2769
  ...(options.tenant
2696
- ? [`eq(schema.${names.pluralCamel}.tenantId, filter.tenantId)`]
2770
+ ? [`eq(schema.${names.pluralCamel}.tenantId, tenantScopeId(scope))`]
2697
2771
  : []),
2698
2772
  ]);
2699
2773
  const drizzleImports = [
@@ -2711,7 +2785,7 @@ function drizzleRepositoryFile(names, config, mode, options, dialect) {
2711
2785
  // After a version-guarded update succeeds, re-select by identity only —
2712
2786
  // matching what `.returning()` yields on the dialects that support it.
2713
2787
  const updateReselectWhere = options.tenant
2714
- ? `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)))`
2715
2789
  : `eq(schema.${names.pluralCamel}.id, input.id)`;
2716
2790
  // drizzle-orm/mysql2 has no `.returning(...)`; mutations resolve to a
2717
2791
  // `[ResultSetHeader, FieldPacket[]]` tuple typed as `unknown` through the
@@ -2732,13 +2806,13 @@ function affectedRows(result: unknown): number {
2732
2806
  `
2733
2807
  : "";
2734
2808
  const createMethod = dialect.supportsReturning
2735
- ? ` async create(input) {
2809
+ ? ` async create(input${options.tenant ? ", scope" : ""}) {
2736
2810
  const now = new Date().toISOString();
2737
2811
  const [row] = await db
2738
2812
  .insert(schema.${names.pluralCamel})
2739
2813
  .values({
2740
2814
  id: crypto.randomUUID(),
2741
- ${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,
2742
2816
  version: 1,
2743
2817
  createdAt: now,
2744
2818
  updatedAt: now,
@@ -2752,11 +2826,11 @@ ${options.tenant ? `\t\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input
2752
2826
  return to${names.singularPascal}(row);
2753
2827
  },
2754
2828
  `
2755
- : ` async create(input) {
2829
+ : ` async create(input${options.tenant ? ", scope" : ""}) {
2756
2830
  const now = new Date().toISOString();
2757
2831
  const ${names.singularCamel}: ${names.singularPascal} = {
2758
2832
  id: crypto.randomUUID(),
2759
- ${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,
2760
2834
  version: 1,
2761
2835
  createdAt: now,
2762
2836
  updatedAt: now,
@@ -2769,7 +2843,7 @@ ${options.tenant ? `\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input.na
2769
2843
  const resourceMethods = mode !== "resource"
2770
2844
  ? ""
2771
2845
  : dialect.supportsReturning
2772
- ? ` async findById(id: string${options.tenant ? ", filter" : ""}) {
2846
+ ? ` async findById(id: string${options.tenant ? ", scope" : ""}) {
2773
2847
  const [row] = await db
2774
2848
  .select()
2775
2849
  .from(schema.${names.pluralCamel})
@@ -2778,7 +2852,7 @@ ${options.tenant ? `\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input.na
2778
2852
 
2779
2853
  return row ? to${names.singularPascal}(row) : null;
2780
2854
  },
2781
- async update(input) {
2855
+ async update(input${options.tenant ? ", scope" : ""}) {
2782
2856
  const [row] = await db
2783
2857
  .update(schema.${names.pluralCamel})
2784
2858
  .set({
@@ -2791,12 +2865,12 @@ ${options.tenant ? `\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input.na
2791
2865
 
2792
2866
  return row ? to${names.singularPascal}(row) : null;
2793
2867
  },
2794
- async delete(id: string${options.tenant ? ", filter" : ""}) {
2868
+ async delete(id: string${options.tenant ? ", scope" : ""}) {
2795
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`}
2796
2870
  return rows.length > 0;
2797
2871
  },
2798
2872
  `
2799
- : ` async findById(id: string${options.tenant ? ", filter" : ""}) {
2873
+ : ` async findById(id: string${options.tenant ? ", scope" : ""}) {
2800
2874
  const [row] = await db
2801
2875
  .select()
2802
2876
  .from(schema.${names.pluralCamel})
@@ -2805,7 +2879,7 @@ ${options.softDelete ? `\t\t\tconst now = new Date().toISOString();\n\t\t\tconst
2805
2879
 
2806
2880
  return row ? to${names.singularPascal}(row) : null;
2807
2881
  },
2808
- async update(input) {
2882
+ async update(input${options.tenant ? ", scope" : ""}) {
2809
2883
  const result = await db
2810
2884
  .update(schema.${names.pluralCamel})
2811
2885
  .set({
@@ -2825,14 +2899,14 @@ ${options.softDelete ? `\t\t\tconst now = new Date().toISOString();\n\t\t\tconst
2825
2899
 
2826
2900
  return row ? to${names.singularPascal}(row) : null;
2827
2901
  },
2828
- async delete(id: string${options.tenant ? ", filter" : ""}) {
2902
+ async delete(id: string${options.tenant ? ", scope" : ""}) {
2829
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`}
2830
2904
  return affectedRows(result) > 0;
2831
2905
  },
2832
2906
  `;
2833
2907
  return `import "@beignet/core/server-only";
2834
2908
  import { cursorPageResult } from "@beignet/core/pagination";
2835
- 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}";
2836
2910
  import { ${drizzleImports.join(", ")} } from "drizzle-orm";
2837
2911
  import type { ${names.singularPascal}Repository } from "${aliasModule(repositoryPortPath)}";
2838
2912
  import { encode${names.singularPascal}Cursor } from "${aliasModule(resourceSchemaFilePath(names, config))}";
@@ -2879,9 +2953,9 @@ function ${names.singularCamel}CursorFilter(
2879
2953
 
2880
2954
  function ${names.singularCamel}ListWhere(
2881
2955
  query: List${names.pluralPascal}Query,
2882
- ): SQL<unknown> | undefined {
2956
+ ${options.tenant ? `\tscope: Parameters<${names.singularPascal}Repository["list"]>[1],\n` : ""}): SQL<unknown> | undefined {
2883
2957
  const filters: SQL<unknown>[] = [
2884
- ${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` : ""} ];
2885
2959
  const cursor = ${names.singularCamel}CursorFilter(query);
2886
2960
 
2887
2961
  if (query.name) {
@@ -2915,8 +2989,8 @@ export function createDrizzle${names.singularPascal}Repository(
2915
2989
  db: ${dialect.dbTypeName}<typeof schema>,
2916
2990
  ): ${names.singularPascal}Repository {
2917
2991
  return {
2918
- async list(query) {
2919
- const where = ${names.singularCamel}ListWhere(query);
2992
+ async list(query${options.tenant ? ", scope" : ""}) {
2993
+ const where = ${names.singularCamel}ListWhere(query${options.tenant ? ", scope" : ""});
2920
2994
  const rows = await db
2921
2995
  .select()
2922
2996
  .from(schema.${names.pluralCamel})
@@ -3077,11 +3151,11 @@ export const ${names.exportName} = useCase
3077
3151
  }
3078
3152
  function useCaseTestFile(names, config) {
3079
3153
  const filePath = useCaseTestFilePath(names, config);
3080
- return `import { describe, expect, it } from "bun:test";
3154
+ return `import { describe, expect, it } from "@/lib/beignet-test";
3081
3155
  import { createUseCaseTester } from "@beignet/core/application";
3082
3156
  import { createInMemoryDevtools } from "@beignet/devtools";
3083
3157
  import { createTestContextFactory, createTestPorts } from "@beignet/core/testing";
3084
- import { createTestAnonymousActor } from "@beignet/core/ports/testing";
3158
+ import { createTestAnonymousActor } from "@beignet/core/testing";
3085
3159
  import type { AppContext } from "${aliasModule(config.paths.appContext)}";
3086
3160
  import { appPorts } from "${aliasModule(config.paths.infrastructurePorts)}";
3087
3161
  import { ${names.exportName} } from "${relativeModule(filePath, useCaseFilePath(names, config))}";
@@ -3235,8 +3309,8 @@ export const ${names.singularCamel}Events = [
3235
3309
  `;
3236
3310
  }
3237
3311
  function resourcePolicyTestFile(names, options) {
3238
- return `import { describe, expect, it } from "bun:test";
3239
- import { createPolicyTester } from "@beignet/core/ports/testing";
3312
+ return `import { describe, expect, it } from "@/lib/beignet-test";
3313
+ import { createPolicyTester } from "@beignet/core/testing";
3240
3314
  import { ${names.singularCamel}Policy } from "../policy";
3241
3315
  import type { ${names.singularPascal} } from "../schemas";
3242
3316
 
@@ -3738,7 +3812,7 @@ function routeGroupFile(names, config, mode, _options) {
3738
3812
  const contractsPath = resourceContractFilePath(names, config);
3739
3813
  const useCasesPath = resourceUseCaseIndexPath(names, config);
3740
3814
  return `import "@beignet/core/server-only";
3741
- import { defineRouteGroup } from "@beignet/next";
3815
+ import { defineRouteGroup } from "@beignet/core/server";
3742
3816
  import type { AppContext } from "${relativeModule(routeFilePath, config.paths.appContext)}";
3743
3817
  import {
3744
3818
  create${names.singularPascal},
@@ -3762,13 +3836,13 @@ function testFile(names, config, mode, options) {
3762
3836
  const repositoryPath = path.join(path.dirname(config.paths.infrastructurePorts), names.pluralKebab, `in-memory-${names.singularKebab}-repository.ts`);
3763
3837
  const serverContextPath = path.join(path.dirname(config.paths.server), "context.ts");
3764
3838
  const requestTarget = "app";
3765
- return `import { describe, expect, it } from "bun:test";
3839
+ return `import { describe, expect, it } from "@/lib/beignet-test";
3766
3840
  import { createUseCaseTester } from "@beignet/core/application";
3767
- ${mode === "resource" ? `import { isAppError } from "@beignet/core/errors";\n` : ""}import { defineRoutes } from "@beignet/web";
3841
+ ${mode === "resource" ? `import { isAppError } from "@beignet/core/errors";\n` : ""}import { defineRoutes } from "@beignet/core/server";
3768
3842
  import { createTestApp } from "@beignet/web/testing";
3769
3843
  import { createInMemoryDevtools } from "@beignet/devtools";
3770
3844
  import { createTestContextFactory, createTestPorts } from "@beignet/core/testing";
3771
- import { ${options.auth ? "createTestUserActor" : "createTestAnonymousActor"}${options.tenant ? ", createTestTenant" : ""} } from "@beignet/core/ports/testing";
3845
+ import { ${options.auth ? "createTestUserActor" : "createTestAnonymousActor"}${options.tenant ? ", createTestTenant" : ""} } from "@beignet/core/testing";
3772
3846
  import type { AppContext } from "${aliasModule(config.paths.appContext)}";
3773
3847
  import { appPorts } from "${aliasModule(config.paths.infrastructurePorts)}";
3774
3848
  import { appContext } from "${aliasModule(serverContextPath)}";