@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/src/make.ts CHANGED
@@ -30,6 +30,7 @@ import {
30
30
  assertUseCaseApp,
31
31
  beignetDependencyVersion,
32
32
  constantCase,
33
+ createListenersRegistrySource,
33
34
  type DatabaseName,
34
35
  type DrizzleDialect,
35
36
  defineUploadsInitializerInfo,
@@ -66,6 +67,7 @@ import {
66
67
  jobFilePath,
67
68
  type ListenerNames,
68
69
  listenerFilePath,
70
+ listenersFilePath,
69
71
  type MakeResult,
70
72
  mergeMakeResultInto,
71
73
  mergeMakeResults,
@@ -115,6 +117,7 @@ import {
115
117
  updateInfrastructureGatePolicies,
116
118
  updateInfrastructurePortStub,
117
119
  updateInfrastructurePorts,
120
+ updateListenersRegistrySource,
118
121
  updateOpenApiRoute,
119
122
  updatePackageDependencies,
120
123
  updatePackageJson,
@@ -138,12 +141,12 @@ import {
138
141
  } from "./make/shared.js";
139
142
  import {
140
143
  appendToArrayExpression,
141
- appendToNamedArray,
142
144
  appendToOutboxRegistryArray,
143
145
  arrayInitializerInfo,
144
146
  identifiersFromArrayExpression,
145
147
  insertAfterImports,
146
148
  } from "./registry-edits.js";
149
+ import { testSupportTemplateFiles } from "./templates/testing.js";
147
150
 
148
151
  export type { MakeFeatureAddon, MakeFeatureRecipe } from "./choices.js";
149
152
  export {
@@ -1241,7 +1244,7 @@ export async function makeSeed(
1241
1244
  if (
1242
1245
  await updatePackageScripts(
1243
1246
  targetDir,
1244
- { "db:seed": `bun ${seedEntrypointPath(config)}` },
1247
+ { "db:seed": `tsx ${seedEntrypointPath(config)}` },
1245
1248
  { dryRun: Boolean(options.dryRun) },
1246
1249
  )
1247
1250
  ) {
@@ -1320,6 +1323,9 @@ export async function makeListener(
1320
1323
  if (!options.skipEventAssert) {
1321
1324
  await assertListenerEventExists(targetDir, names.event, config);
1322
1325
  }
1326
+ await updateListenerRegistry(targetDir, names, config, {
1327
+ dryRun: true,
1328
+ });
1323
1329
  await updateListenerProviderWiring(targetDir, names, config, {
1324
1330
  dryRun: true,
1325
1331
  });
@@ -1340,6 +1346,24 @@ export async function makeListener(
1340
1346
  },
1341
1347
  });
1342
1348
 
1349
+ const registryResult = await updateListenerRegistry(
1350
+ targetDir,
1351
+ names,
1352
+ config,
1353
+ {
1354
+ dryRun: Boolean(options.dryRun),
1355
+ },
1356
+ );
1357
+ if (registryResult === "created")
1358
+ result.createdFiles.push(listenersFilePath(config));
1359
+ if (registryResult === "updated")
1360
+ result.updatedFiles.push(listenersFilePath(config));
1361
+ if (registryResult === "skipped")
1362
+ result.skippedFiles.push(listenersFilePath(config));
1363
+ if (!result.files.includes(listenersFilePath(config))) {
1364
+ result.files.push(listenersFilePath(config));
1365
+ }
1366
+
1343
1367
  const providerResult = await updateListenerProviderWiring(
1344
1368
  targetDir,
1345
1369
  names,
@@ -1361,6 +1385,49 @@ export async function makeListener(
1361
1385
  return result;
1362
1386
  }
1363
1387
 
1388
+ async function updateListenerRegistry(
1389
+ targetDir: string,
1390
+ names: ListenerNames,
1391
+ config: ResolvedBeignetConfig,
1392
+ options: { dryRun: boolean },
1393
+ ): Promise<WriteGeneratedFileResult> {
1394
+ const file = listenersFilePath(config);
1395
+ const filePath = path.join(targetDir, file);
1396
+ const listenerIndex = featureArtifactIndexFile("listener", names, config);
1397
+ const original = await readOptionalFile(filePath);
1398
+
1399
+ if (original === undefined) {
1400
+ if (!options.dryRun) {
1401
+ await writeGeneratedFile(
1402
+ targetDir,
1403
+ {
1404
+ path: file,
1405
+ content: createListenersRegistrySource({
1406
+ registryName: listenerIndex.registryName,
1407
+ listenerModule: aliasModule(listenerIndex.path),
1408
+ }),
1409
+ },
1410
+ { force: false, dryRun: false },
1411
+ );
1412
+ }
1413
+ return "created";
1414
+ }
1415
+
1416
+ const result = updateListenersRegistrySource(original, {
1417
+ registryName: listenerIndex.registryName,
1418
+ listenerModule: aliasModule(listenerIndex.path),
1419
+ });
1420
+ if (result.kind === "missing") {
1421
+ throw new Error(
1422
+ `Could not find the exported listeners array in ${file}. Register ${listenerIndex.registryName} manually, or restore the generated listeners file before running make listener.`,
1423
+ );
1424
+ }
1425
+
1426
+ if (result.kind === "unchanged") return "skipped";
1427
+ if (!options.dryRun) await writeFile(filePath, result.source);
1428
+ return "updated";
1429
+ }
1430
+
1364
1431
  async function assertListenerEventExists(
1365
1432
  targetDir: string,
1366
1433
  names: EventNames,
@@ -1702,6 +1769,7 @@ async function updateOutboxPorts(
1702
1769
  const filePath = path.join(targetDir, config.paths.ports);
1703
1770
  const original = await readFile(filePath, "utf8");
1704
1771
  let next = addNamedTypeImport(original, "OutboxPort", "@beignet/core/outbox");
1772
+ next = addNamedTypeImport(next, "OutboxAdminPort", "@beignet/core/outbox");
1705
1773
  if (!typeHasTopLevelProperty(next, "AppPorts", "outbox")) {
1706
1774
  next = insertTypeProperty(
1707
1775
  next,
@@ -1710,6 +1778,14 @@ async function updateOutboxPorts(
1710
1778
  `Could not find AppPorts in ${config.paths.ports}. Add outbox: OutboxPort; manually, or restore the generated ports file before running make outbox.`,
1711
1779
  );
1712
1780
  }
1781
+ if (!typeHasTopLevelProperty(next, "AppPorts", "outboxAdmin")) {
1782
+ next = insertTypeProperty(
1783
+ next,
1784
+ "AppPorts",
1785
+ "outboxAdmin: OutboxAdminPort;",
1786
+ `Could not find AppPorts in ${config.paths.ports}. Add outboxAdmin: OutboxAdminPort; manually, or restore the generated ports file before running make outbox.`,
1787
+ );
1788
+ }
1713
1789
  if (!typeHasTopLevelProperty(next, "AppTransactionPorts", "outbox")) {
1714
1790
  next = insertTypeProperty(
1715
1791
  next,
@@ -1731,7 +1807,8 @@ async function updateOutboxInfrastructurePorts(
1731
1807
  ): Promise<boolean> {
1732
1808
  const filePath = path.join(targetDir, config.paths.infrastructurePorts);
1733
1809
  const original = await readFile(filePath, "utf8");
1734
- const next = appendDeferredPortKey(original, "outbox");
1810
+ let next = appendDeferredPortKey(original, "outbox");
1811
+ next = appendDeferredPortKey(next, "outboxAdmin");
1735
1812
 
1736
1813
  if (next === original) return false;
1737
1814
  if (!options.dryRun) await writeFile(filePath, next);
@@ -1749,13 +1826,20 @@ async function updateOutboxDatabaseProvider(
1749
1826
 
1750
1827
  const database = await detectResourceDatabase(targetDir, config);
1751
1828
  const factoryName = drizzleOutboxPortFactoryName(database);
1829
+ const adminFactoryName = drizzleOutboxAdminPortFactoryName(database);
1752
1830
  const original = await readFile(filePath, "utf8");
1753
1831
  let next = addNamedImport(
1754
1832
  original,
1755
1833
  factoryName,
1756
1834
  `@beignet/provider-db-drizzle/${database}`,
1757
1835
  );
1836
+ next = addNamedImport(
1837
+ next,
1838
+ adminFactoryName,
1839
+ `@beignet/provider-db-drizzle/${database}`,
1840
+ );
1758
1841
  next = appendPickStringLiteralMember(next, "AppPorts", "outbox");
1842
+ next = appendPickStringLiteralMember(next, "AppPorts", "outboxAdmin");
1759
1843
 
1760
1844
  if (!/\bconst\s+outbox\s*=/.test(next)) {
1761
1845
  const beforeOutboxConst = next;
@@ -1770,6 +1854,21 @@ async function updateOutboxDatabaseProvider(
1770
1854
  }
1771
1855
  }
1772
1856
 
1857
+ if (!/\bconst\s+outboxAdmin\s*=/.test(next)) {
1858
+ const beforeOutboxAdminConst = next;
1859
+ next = beforeOutboxAdminConst.replace(
1860
+ new RegExp(
1861
+ `(\\n\\s*const outbox = ${factoryName}\\((dbPort\\.(?:drizzle|db))\\);)`,
1862
+ ),
1863
+ `$1\n\t\tconst outboxAdmin = ${adminFactoryName}($2);`,
1864
+ );
1865
+ if (next === beforeOutboxAdminConst) {
1866
+ throw new Error(
1867
+ `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.`,
1868
+ );
1869
+ }
1870
+ }
1871
+
1773
1872
  if (!/\boutbox\s*,/.test(next)) {
1774
1873
  const withOutboxPort = next.replace(
1775
1874
  /(\n\s*idempotency,\n)/,
@@ -1783,6 +1882,19 @@ async function updateOutboxDatabaseProvider(
1783
1882
  next = withOutboxPort;
1784
1883
  }
1785
1884
 
1885
+ if (!/\boutboxAdmin\s*,/.test(next)) {
1886
+ const withOutboxAdminPort = next.replace(
1887
+ /(\n\s*outbox,\n)/,
1888
+ `$1\t\t\toutboxAdmin,\n`,
1889
+ );
1890
+ if (withOutboxAdminPort === next) {
1891
+ throw new Error(
1892
+ `Could not find outbox in providedPorts in ${file}. Add outboxAdmin to providedPorts manually, or restore the generated database provider before running make outbox.`,
1893
+ );
1894
+ }
1895
+ next = withOutboxAdminPort;
1896
+ }
1897
+
1786
1898
  if (!new RegExp(`outbox:\\s*${factoryName}\\(tx\\)`).test(next)) {
1787
1899
  const withTransactionOutbox = next.replace(
1788
1900
  /(\n\s*idempotency: createDrizzle[A-Za-z]+IdempotencyPort\(tx\),\n)/,
@@ -1882,6 +1994,12 @@ function drizzleOutboxPortFactoryName(database: DatabaseName): string {
1882
1994
  return "createDrizzleSqliteOutboxPort";
1883
1995
  }
1884
1996
 
1997
+ function drizzleOutboxAdminPortFactoryName(database: DatabaseName): string {
1998
+ if (database === "postgres") return "createDrizzlePostgresOutboxAdminPort";
1999
+ if (database === "mysql") return "createDrizzleMysqlOutboxAdminPort";
2000
+ return "createDrizzleSqliteOutboxAdminPort";
2001
+ }
2002
+
1885
2003
  async function applyOutboxDrainWiring(
1886
2004
  result: MakeResult,
1887
2005
  targetDir: string,
@@ -1957,7 +2075,10 @@ export async function makeUpload(
1957
2075
  force: Boolean(options.force),
1958
2076
  dryRun: Boolean(options.dryRun),
1959
2077
  name: `${names.feature.kebab}/${names.artifact.kebab}`,
1960
- files: uploadFiles(names, config),
2078
+ files: [
2079
+ ...(await missingCapabilityBuilderFiles(targetDir, "uploads", config)),
2080
+ ...uploadFiles(names, config),
2081
+ ],
1961
2082
  index: featureArtifactIndexFile("upload", names, config),
1962
2083
  dependencies: {
1963
2084
  "@beignet/core": beignetDependencyVersion,
@@ -2512,6 +2633,7 @@ function resourceFiles(
2512
2633
  const infraDir = infrastructureDir(config);
2513
2634
  const featureDir = resourceFeatureDir(names, config);
2514
2635
  const files: GeneratedFile[] = [
2636
+ ...testSupportTemplateFiles,
2515
2637
  {
2516
2638
  path: resourceContractFilePath(names, config),
2517
2639
  content: contractFile(names, config, mode, options),
@@ -2668,6 +2790,7 @@ function testFiles(
2668
2790
  config: ResolvedBeignetConfig,
2669
2791
  ): GeneratedFile[] {
2670
2792
  return [
2793
+ ...testSupportTemplateFiles,
2671
2794
  {
2672
2795
  path: useCaseTestFilePath(names, config),
2673
2796
  content: useCaseTestFile(names, config),
@@ -3468,7 +3591,7 @@ function listUseCaseFile(
3468
3591
 
3469
3592
  return `import "@beignet/core/server-only";
3470
3593
  import { normalizeCursorPage } from "@beignet/core/pagination";
3471
- import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
3594
+ ${options.tenant ? `import { requireTenantScope } from "@beignet/core/tenancy";\n` : ""}import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
3472
3595
  import {
3473
3596
  decode${names.singularPascal}Cursor,
3474
3597
  List${names.pluralPascal}InputSchema,
@@ -3480,7 +3603,7 @@ export const list${names.pluralPascal}UseCase = useCase
3480
3603
  .input(List${names.pluralPascal}InputSchema)
3481
3604
  .output(List${names.pluralPascal}OutputSchema)
3482
3605
  .run(async ({ ctx, input }) => {
3483
- ${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, {
3606
+ ${options.tenant ? `\t\tconst scope = requireTenantScope(ctx);\n\n` : ""} const page = normalizeCursorPage(input, {
3484
3607
  defaultLimit: 20,
3485
3608
  maxLimit: 100,
3486
3609
  });
@@ -3491,7 +3614,7 @@ ${options.tenant ? `\t\tconst tenantId = ctx.tenant?.id;\n\t\tif (!tenantId) {\n
3491
3614
  name: input.name,
3492
3615
  sortBy: input.sortBy,
3493
3616
  sortDirection: input.sortDirection,
3494
- ${options.tenant ? `\t\t\ttenantId,\n` : ""} });
3617
+ }${options.tenant ? ", scope" : ""});
3495
3618
  });
3496
3619
  `;
3497
3620
  }
@@ -3507,7 +3630,7 @@ function createUseCaseFile(
3507
3630
  );
3508
3631
 
3509
3632
  return `import "@beignet/core/server-only";
3510
- import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
3633
+ ${options.tenant ? `import { requireTenantScope } from "@beignet/core/tenancy";\n` : ""}import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
3511
3634
  import {
3512
3635
  Create${names.singularPascal}InputSchema,
3513
3636
  ${names.singularPascal}Schema,
@@ -3519,9 +3642,7 @@ export const create${names.singularPascal}UseCase = useCase
3519
3642
  .input(Create${names.singularPascal}InputSchema)
3520
3643
  .output(${names.singularPascal}Schema)
3521
3644
  .run(async ({ ctx, input }) => {
3522
- ${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({
3523
- ...input,
3524
- ${options.tenant ? `\t\t\ttenantId,\n` : ""} });
3645
+ ${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" : ""});
3525
3646
  ${options.events ? `\n\t\tawait ctx.ports.eventBus.publish(${names.singularPascal}Created, {\n\t\t\tid: ${names.singularCamel}.id,\n\t\t});\n` : ""}
3526
3647
  return ${names.singularCamel};
3527
3648
  });
@@ -3539,7 +3660,7 @@ function getUseCaseFile(
3539
3660
  );
3540
3661
 
3541
3662
  return `import "@beignet/core/server-only";
3542
- import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
3663
+ ${options.tenant ? `import { requireTenantScope } from "@beignet/core/tenancy";\n` : ""}import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
3543
3664
  import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
3544
3665
  import {
3545
3666
  ${names.singularPascal}IdInputSchema,
@@ -3551,7 +3672,7 @@ export const get${names.singularPascal}UseCase = useCase
3551
3672
  .input(${names.singularPascal}IdInputSchema)
3552
3673
  .output(${names.singularPascal}Schema)
3553
3674
  .run(async ({ ctx, input }) => {
3554
- ${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 }" : ""});
3675
+ ${options.tenant ? `\t\tconst scope = requireTenantScope(ctx);\n\n` : ""} const ${names.singularCamel} = await ctx.ports.${names.pluralCamel}.findById(input.id${options.tenant ? ", scope" : ""});
3555
3676
  if (!${names.singularCamel}) {
3556
3677
  throw appError("${names.singularPascal}NotFound", {
3557
3678
  details: { id: input.id },
@@ -3574,7 +3695,7 @@ function updateUseCaseFile(
3574
3695
  );
3575
3696
 
3576
3697
  return `import "@beignet/core/server-only";
3577
- import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
3698
+ ${options.tenant ? `import { requireTenantScope } from "@beignet/core/tenancy";\n` : ""}import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
3578
3699
  import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
3579
3700
  ${options.events ? `import { ${names.singularPascal}Updated } from "../domain/events";\n` : ""}import {
3580
3701
  Update${names.singularPascal}InputSchema,
@@ -3586,7 +3707,7 @@ export const update${names.singularPascal}UseCase = useCase
3586
3707
  .input(Update${names.singularPascal}InputSchema)
3587
3708
  .output(${names.singularPascal}Schema)
3588
3709
  .run(async ({ ctx, input }) => {
3589
- ${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 }" : ""});
3710
+ ${options.tenant ? `\t\tconst scope = requireTenantScope(ctx);\n\n` : ""} const existing = await ctx.ports.${names.pluralCamel}.findById(input.id${options.tenant ? ", scope" : ""});
3590
3711
  if (!existing) {
3591
3712
  throw appError("${names.singularPascal}NotFound", {
3592
3713
  details: { id: input.id },
@@ -3600,7 +3721,7 @@ ${options.auth ? `\t\tawait ctx.gate.authorize("${names.pluralCamel}.update", ex
3600
3721
 
3601
3722
  const ${names.singularCamel} = await ctx.ports.${names.pluralCamel}.update({
3602
3723
  ...input,
3603
- ${options.tenant ? `\t\t\ttenantId,\n` : ""} });
3724
+ }${options.tenant ? ", scope" : ""});
3604
3725
  if (!${names.singularCamel}) {
3605
3726
  throw appError("${names.singularPascal}Conflict", {
3606
3727
  details: { id: input.id, expectedVersion: existing.version },
@@ -3624,7 +3745,7 @@ function deleteUseCaseFile(
3624
3745
 
3625
3746
  return `import "@beignet/core/server-only";
3626
3747
  import { z } from "zod";
3627
- import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
3748
+ ${options.tenant ? `import { requireTenantScope } from "@beignet/core/tenancy";\n` : ""}import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
3628
3749
  import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
3629
3750
  ${options.events ? `import { ${names.singularPascal}Deleted } from "../domain/events";\n` : ""}import { ${names.singularPascal}IdInputSchema } from "../schemas";
3630
3751
 
@@ -3633,7 +3754,7 @@ export const delete${names.singularPascal}UseCase = useCase
3633
3754
  .input(${names.singularPascal}IdInputSchema)
3634
3755
  .output(z.void())
3635
3756
  .run(async ({ ctx, input }) => {
3636
- ${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 }" : ""});
3757
+ ${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" : ""});
3637
3758
  if (!deleted) {
3638
3759
  throw appError("${names.singularPascal}NotFound", {
3639
3760
  details: { id: input.id },
@@ -3673,7 +3794,7 @@ function repositoryPortFile(
3673
3794
  PageResult,
3674
3795
  SortDirection,
3675
3796
  } from "@beignet/core/pagination";
3676
- import type {
3797
+ ${options.tenant ? `import type { TenantScope } from "@beignet/core/tenancy";\n` : ""}import type {
3677
3798
  Create${names.singularPascal}Input,
3678
3799
  ${names.singularPascal}Cursor,
3679
3800
  ${names.singularPascal}SortBy,
@@ -3687,13 +3808,12 @@ export type List${names.pluralPascal}Query = {
3687
3808
  name?: string;
3688
3809
  sortBy: ${names.singularPascal}SortBy;
3689
3810
  sortDirection: SortDirection;
3690
- ${options.tenant ? `\ttenantId: string;\n` : ""}};
3691
- ${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` : ""}` : ""}
3811
+ };
3692
3812
 
3693
3813
  export interface ${names.singularPascal}Repository {
3694
- list(query: List${names.pluralPascal}Query): Promise<List${names.pluralPascal}Result>;
3695
- create(input: ${options.tenant ? `Create${names.singularPascal}RepositoryInput` : `Create${names.singularPascal}Input`}): Promise<${names.singularPascal}>;
3696
- ${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` : ""}}
3814
+ list(query: List${names.pluralPascal}Query${options.tenant ? ", scope: TenantScope" : ""}): Promise<List${names.pluralPascal}Result>;
3815
+ create(input: Create${names.singularPascal}Input${options.tenant ? ", scope: TenantScope" : ""}): Promise<${names.singularPascal}>;
3816
+ ${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` : ""}}
3697
3817
  `;
3698
3818
  }
3699
3819
 
@@ -3727,14 +3847,14 @@ function inMemoryRepositoryFile(
3727
3847
  ? " || existing.deletedAt !== null"
3728
3848
  : "";
3729
3849
  const deleteImplementation = options.softDelete
3730
- ? `${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();
3850
+ ? `${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();
3731
3851
  ${names.pluralCamel}.set(id, { ...existing, deletedAt: now, updatedAt: now });
3732
3852
  return true;`
3733
- : `${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);`;
3853
+ : `${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);`;
3734
3854
 
3735
3855
  return `import "@beignet/core/server-only";
3736
3856
  import { cursorPageResult } from "@beignet/core/pagination";
3737
- import type { ${names.singularPascal}Repository } from "${aliasModule(repositoryPortPath)}";
3857
+ ${options.tenant ? `import { tenantScopeId } from "@beignet/core/tenancy";\n` : ""}import type { ${names.singularPascal}Repository } from "${aliasModule(repositoryPortPath)}";
3738
3858
  import { encode${names.singularPascal}Cursor } from "${aliasModule(resourceSchemaFilePath(names, config))}";
3739
3859
  import type {
3740
3860
  Create${names.singularPascal}Input,
@@ -3802,10 +3922,10 @@ export function createInMemory${names.singularPascal}Repository(
3802
3922
  );
3803
3923
 
3804
3924
  return {
3805
- async list(query) {
3925
+ async list(query${options.tenant ? ", scope" : ""}) {
3806
3926
  const name = query.name?.toLocaleLowerCase();
3807
- const all${names.pluralPascal} = Array.from(${names.pluralCamel}.values())
3808
- ${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))
3927
+ ${options.tenant ? `\t\t\tconst tenantId = tenantScopeId(scope);\n` : ""} const all${names.pluralPascal} = Array.from(${names.pluralCamel}.values())
3928
+ ${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))
3809
3929
  .sort((left, right) => compare${names.pluralPascal}(left, right, query))
3810
3930
  .filter((${names.singularCamel}) => isAfter${names.singularPascal}Cursor(${names.singularCamel}, query));
3811
3931
  const pageItems = all${names.pluralPascal}.slice(0, query.page.limit);
@@ -3820,11 +3940,11 @@ ${activeFilter}${options.tenant ? `\t\t\t\t.filter((${names.singularCamel}) => $
3820
3940
  nextCursor,
3821
3941
  );
3822
3942
  },
3823
- async create(input) {
3943
+ async create(input${options.tenant ? ", scope" : ""}) {
3824
3944
  const now = new Date().toISOString();
3825
- const ${names.singularCamel}: ${names.singularPascal} = {
3945
+ ${options.tenant ? `\t\t\tconst tenantId = tenantScopeId(scope);\n` : ""} const ${names.singularCamel}: ${names.singularPascal} = {
3826
3946
  id: crypto.randomUUID(),
3827
- ${options.tenant ? `\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input.name,
3947
+ ${options.tenant ? `\t\t\t\ttenantId,\n` : ""} name: input.name,
3828
3948
  version: 1,
3829
3949
  createdAt: now,
3830
3950
  updatedAt: now,
@@ -3834,7 +3954,7 @@ ${options.tenant ? `\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input.na
3834
3954
  ${newDeletedAtField} });
3835
3955
  return ${names.singularCamel};
3836
3956
  },
3837
- ${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` : ""}
3957
+ ${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` : ""}
3838
3958
  };
3839
3959
  }
3840
3960
  `;
@@ -3958,20 +4078,20 @@ function drizzleRepositoryFile(
3958
4078
  const findWhere = drizzleResourceWhere(names, options, [
3959
4079
  `eq(schema.${names.pluralCamel}.id, id)`,
3960
4080
  ...(options.tenant
3961
- ? [`eq(schema.${names.pluralCamel}.tenantId, filter.tenantId)`]
4081
+ ? [`eq(schema.${names.pluralCamel}.tenantId, tenantScopeId(scope))`]
3962
4082
  : []),
3963
4083
  ]);
3964
4084
  const updateWhere = drizzleResourceWhere(names, options, [
3965
4085
  `eq(schema.${names.pluralCamel}.id, input.id)`,
3966
4086
  `eq(schema.${names.pluralCamel}.version, input.version)`,
3967
4087
  ...(options.tenant
3968
- ? [`eq(schema.${names.pluralCamel}.tenantId, input.tenantId)`]
4088
+ ? [`eq(schema.${names.pluralCamel}.tenantId, tenantScopeId(scope))`]
3969
4089
  : []),
3970
4090
  ]);
3971
4091
  const deleteWhere = drizzleResourceWhere(names, options, [
3972
4092
  `eq(schema.${names.pluralCamel}.id, id)`,
3973
4093
  ...(options.tenant
3974
- ? [`eq(schema.${names.pluralCamel}.tenantId, filter.tenantId)`]
4094
+ ? [`eq(schema.${names.pluralCamel}.tenantId, tenantScopeId(scope))`]
3975
4095
  : []),
3976
4096
  ]);
3977
4097
  const drizzleImports = [
@@ -3989,7 +4109,7 @@ function drizzleRepositoryFile(
3989
4109
  // After a version-guarded update succeeds, re-select by identity only —
3990
4110
  // matching what `.returning()` yields on the dialects that support it.
3991
4111
  const updateReselectWhere = options.tenant
3992
- ? `and(eq(schema.${names.pluralCamel}.id, input.id), eq(schema.${names.pluralCamel}.tenantId, input.tenantId))`
4112
+ ? `and(eq(schema.${names.pluralCamel}.id, input.id), eq(schema.${names.pluralCamel}.tenantId, tenantScopeId(scope)))`
3993
4113
  : `eq(schema.${names.pluralCamel}.id, input.id)`;
3994
4114
  // drizzle-orm/mysql2 has no `.returning(...)`; mutations resolve to a
3995
4115
  // `[ResultSetHeader, FieldPacket[]]` tuple typed as `unknown` through the
@@ -4011,13 +4131,13 @@ function affectedRows(result: unknown): number {
4011
4131
  `
4012
4132
  : "";
4013
4133
  const createMethod = dialect.supportsReturning
4014
- ? ` async create(input) {
4134
+ ? ` async create(input${options.tenant ? ", scope" : ""}) {
4015
4135
  const now = new Date().toISOString();
4016
4136
  const [row] = await db
4017
4137
  .insert(schema.${names.pluralCamel})
4018
4138
  .values({
4019
4139
  id: crypto.randomUUID(),
4020
- ${options.tenant ? `\t\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input.name,
4140
+ ${options.tenant ? `\t\t\t\t\ttenantId: tenantScopeId(scope),\n` : ""} name: input.name,
4021
4141
  version: 1,
4022
4142
  createdAt: now,
4023
4143
  updatedAt: now,
@@ -4031,11 +4151,11 @@ ${options.tenant ? `\t\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input
4031
4151
  return to${names.singularPascal}(row);
4032
4152
  },
4033
4153
  `
4034
- : ` async create(input) {
4154
+ : ` async create(input${options.tenant ? ", scope" : ""}) {
4035
4155
  const now = new Date().toISOString();
4036
4156
  const ${names.singularCamel}: ${names.singularPascal} = {
4037
4157
  id: crypto.randomUUID(),
4038
- ${options.tenant ? `\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input.name,
4158
+ ${options.tenant ? `\t\t\t\ttenantId: tenantScopeId(scope),\n` : ""} name: input.name,
4039
4159
  version: 1,
4040
4160
  createdAt: now,
4041
4161
  updatedAt: now,
@@ -4049,7 +4169,7 @@ ${options.tenant ? `\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input.na
4049
4169
  mode !== "resource"
4050
4170
  ? ""
4051
4171
  : dialect.supportsReturning
4052
- ? ` async findById(id: string${options.tenant ? ", filter" : ""}) {
4172
+ ? ` async findById(id: string${options.tenant ? ", scope" : ""}) {
4053
4173
  const [row] = await db
4054
4174
  .select()
4055
4175
  .from(schema.${names.pluralCamel})
@@ -4058,7 +4178,7 @@ ${options.tenant ? `\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input.na
4058
4178
 
4059
4179
  return row ? to${names.singularPascal}(row) : null;
4060
4180
  },
4061
- async update(input) {
4181
+ async update(input${options.tenant ? ", scope" : ""}) {
4062
4182
  const [row] = await db
4063
4183
  .update(schema.${names.pluralCamel})
4064
4184
  .set({
@@ -4071,12 +4191,12 @@ ${options.tenant ? `\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input.na
4071
4191
 
4072
4192
  return row ? to${names.singularPascal}(row) : null;
4073
4193
  },
4074
- async delete(id: string${options.tenant ? ", filter" : ""}) {
4194
+ async delete(id: string${options.tenant ? ", scope" : ""}) {
4075
4195
  ${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`}
4076
4196
  return rows.length > 0;
4077
4197
  },
4078
4198
  `
4079
- : ` async findById(id: string${options.tenant ? ", filter" : ""}) {
4199
+ : ` async findById(id: string${options.tenant ? ", scope" : ""}) {
4080
4200
  const [row] = await db
4081
4201
  .select()
4082
4202
  .from(schema.${names.pluralCamel})
@@ -4085,7 +4205,7 @@ ${options.softDelete ? `\t\t\tconst now = new Date().toISOString();\n\t\t\tconst
4085
4205
 
4086
4206
  return row ? to${names.singularPascal}(row) : null;
4087
4207
  },
4088
- async update(input) {
4208
+ async update(input${options.tenant ? ", scope" : ""}) {
4089
4209
  const result = await db
4090
4210
  .update(schema.${names.pluralCamel})
4091
4211
  .set({
@@ -4105,7 +4225,7 @@ ${options.softDelete ? `\t\t\tconst now = new Date().toISOString();\n\t\t\tconst
4105
4225
 
4106
4226
  return row ? to${names.singularPascal}(row) : null;
4107
4227
  },
4108
- async delete(id: string${options.tenant ? ", filter" : ""}) {
4228
+ async delete(id: string${options.tenant ? ", scope" : ""}) {
4109
4229
  ${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`}
4110
4230
  return affectedRows(result) > 0;
4111
4231
  },
@@ -4113,7 +4233,7 @@ ${options.softDelete ? `\t\t\tconst now = new Date().toISOString();\n\t\t\tconst
4113
4233
 
4114
4234
  return `import "@beignet/core/server-only";
4115
4235
  import { cursorPageResult } from "@beignet/core/pagination";
4116
- import type { ${dialect.dbTypeName} } from "@beignet/provider-db-drizzle/${dialect.subpath}";
4236
+ ${options.tenant ? `import { tenantScopeId } from "@beignet/core/tenancy";\n` : ""}import type { ${dialect.dbTypeName} } from "@beignet/provider-db-drizzle/${dialect.subpath}";
4117
4237
  import { ${drizzleImports.join(", ")} } from "drizzle-orm";
4118
4238
  import type { ${names.singularPascal}Repository } from "${aliasModule(repositoryPortPath)}";
4119
4239
  import { encode${names.singularPascal}Cursor } from "${aliasModule(resourceSchemaFilePath(names, config))}";
@@ -4160,9 +4280,9 @@ function ${names.singularCamel}CursorFilter(
4160
4280
 
4161
4281
  function ${names.singularCamel}ListWhere(
4162
4282
  query: List${names.pluralPascal}Query,
4163
- ): SQL<unknown> | undefined {
4283
+ ${options.tenant ? `\tscope: Parameters<${names.singularPascal}Repository["list"]>[1],\n` : ""}): SQL<unknown> | undefined {
4164
4284
  const filters: SQL<unknown>[] = [
4165
- ${options.tenant ? `\t\teq(schema.${names.pluralCamel}.tenantId, query.tenantId),\n` : ""}${options.softDelete ? `\t\tisNull(schema.${names.pluralCamel}.deletedAt),\n` : ""} ];
4285
+ ${options.tenant ? `\t\teq(schema.${names.pluralCamel}.tenantId, tenantScopeId(scope)),\n` : ""}${options.softDelete ? `\t\tisNull(schema.${names.pluralCamel}.deletedAt),\n` : ""} ];
4166
4286
  const cursor = ${names.singularCamel}CursorFilter(query);
4167
4287
 
4168
4288
  if (query.name) {
@@ -4196,8 +4316,8 @@ export function createDrizzle${names.singularPascal}Repository(
4196
4316
  db: ${dialect.dbTypeName}<typeof schema>,
4197
4317
  ): ${names.singularPascal}Repository {
4198
4318
  return {
4199
- async list(query) {
4200
- const where = ${names.singularCamel}ListWhere(query);
4319
+ async list(query${options.tenant ? ", scope" : ""}) {
4320
+ const where = ${names.singularCamel}ListWhere(query${options.tenant ? ", scope" : ""});
4201
4321
  const rows = await db
4202
4322
  .select()
4203
4323
  .from(schema.${names.pluralCamel})
@@ -4377,7 +4497,7 @@ function useCaseTestFile(
4377
4497
  ): string {
4378
4498
  const filePath = useCaseTestFilePath(names, config);
4379
4499
 
4380
- return `import { describe, expect, it } from "bun:test";
4500
+ return `import { describe, expect, it } from "@/lib/beignet-test";
4381
4501
  import { createUseCaseTester } from "@beignet/core/application";
4382
4502
  import { createInMemoryDevtools } from "@beignet/devtools";
4383
4503
  import { createTestContextFactory, createTestPorts } from "@beignet/core/testing";
@@ -4549,7 +4669,7 @@ function resourcePolicyTestFile(
4549
4669
  names: ResourceNames,
4550
4670
  options: ResourceGenerationOptions,
4551
4671
  ): string {
4552
- return `import { describe, expect, it } from "bun:test";
4672
+ return `import { describe, expect, it } from "@/lib/beignet-test";
4553
4673
  import { createPolicyTester } from "@beignet/core/ports/testing";
4554
4674
  import { ${names.singularCamel}Policy } from "../policy";
4555
4675
  import type { ${names.singularPascal} } from "../schemas";
@@ -4800,9 +4920,8 @@ ${names.timezone ? `\t\ttimezone: "${names.timezone}",\n` : ""}\t\tpayload: ${na
4800
4920
  }
4801
4921
 
4802
4922
  function uploadFile(names: UploadNames, config: ResolvedBeignetConfig): string {
4803
- return `import { defineUpload } from "@beignet/core/uploads";
4804
- import { z } from "zod";
4805
- import type { AppContext } from "${aliasModule(config.paths.appContext)}";
4923
+ return `import { z } from "zod";
4924
+ import { defineUpload } from "${aliasModule(config.paths.uploadsBuilder)}";
4806
4925
 
4807
4926
  export const ${names.metadataSchemaName} = z.object({
4808
4927
  \tresourceId: z.string().min(1),
@@ -4810,12 +4929,7 @@ export const ${names.metadataSchemaName} = z.object({
4810
4929
 
4811
4930
  export type ${names.metadataTypeName} = z.infer<typeof ${names.metadataSchemaName}>;
4812
4931
 
4813
- export const ${names.uploadExportName} = defineUpload<
4814
- \t"${names.uploadName}",
4815
- \ttypeof ${names.metadataSchemaName},
4816
- \tAppContext,
4817
- \t{ objectKeys: string[] }
4818
- >("${names.uploadName}", {
4932
+ export const ${names.uploadExportName} = defineUpload("${names.uploadName}", {
4819
4933
  \tmetadata: ${names.metadataSchemaName},
4820
4934
  \tfile: {
4821
4935
  \t\tcontentTypes: ["application/pdf", "text/plain"],
@@ -5173,7 +5287,7 @@ function testFile(
5173
5287
  );
5174
5288
  const requestTarget = "app";
5175
5289
 
5176
- return `import { describe, expect, it } from "bun:test";
5290
+ return `import { describe, expect, it } from "@/lib/beignet-test";
5177
5291
  import { createUseCaseTester } from "@beignet/core/application";
5178
5292
  ${mode === "resource" ? `import { isAppError } from "@beignet/core/errors";\n` : ""}import { defineRoutes } from "@beignet/web";
5179
5293
  import { createTestApp } from "@beignet/web/testing";