@beignet/cli 0.0.32 → 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.
- package/CHANGELOG.md +49 -0
- package/README.md +26 -16
- package/dist/choices.d.ts +3 -3
- package/dist/choices.d.ts.map +1 -1
- package/dist/choices.js +2 -0
- package/dist/choices.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/db.js +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +279 -1
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +797 -58
- package/dist/inspect.js.map +1 -1
- package/dist/make/inbox.d.ts.map +1 -1
- package/dist/make/inbox.js +5 -3
- package/dist/make/inbox.js.map +1 -1
- package/dist/make/payments.d.ts.map +1 -1
- package/dist/make/payments.js +58 -29
- package/dist/make/payments.js.map +1 -1
- package/dist/make/shared.d.ts +11 -2
- package/dist/make/shared.d.ts.map +1 -1
- package/dist/make/shared.js +44 -10
- package/dist/make/shared.js.map +1 -1
- package/dist/make/tenancy.d.ts.map +1 -1
- package/dist/make/tenancy.js +6 -4
- package/dist/make/tenancy.js.map +1 -1
- package/dist/make.d.ts.map +1 -1
- package/dist/make.js +129 -55
- package/dist/make.js.map +1 -1
- package/dist/outbox.d.ts +114 -1
- package/dist/outbox.d.ts.map +1 -1
- package/dist/outbox.js +190 -0
- package/dist/outbox.js.map +1 -1
- package/dist/preflight.js +1 -1
- package/dist/preflight.js.map +1 -1
- package/dist/provider-add.d.ts.map +1 -1
- package/dist/provider-add.js +46 -0
- package/dist/provider-add.js.map +1 -1
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js +2 -3
- package/dist/task.js.map +1 -1
- package/dist/templates/agents.d.ts.map +1 -1
- package/dist/templates/agents.js +6 -3
- package/dist/templates/agents.js.map +1 -1
- package/dist/templates/base.js +3 -3
- package/dist/templates/base.js.map +1 -1
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +2 -0
- package/dist/templates/index.js.map +1 -1
- package/dist/templates/server.d.ts.map +1 -1
- package/dist/templates/server.js +6 -1
- package/dist/templates/server.js.map +1 -1
- package/dist/templates/shared.d.ts +3 -1
- package/dist/templates/shared.d.ts.map +1 -1
- package/dist/templates/shared.js +11 -1
- package/dist/templates/shared.js.map +1 -1
- package/dist/templates/testing.d.ts +5 -0
- package/dist/templates/testing.d.ts.map +1 -0
- package/dist/templates/testing.js +542 -0
- package/dist/templates/testing.js.map +1 -0
- package/dist/templates/todos.js +2 -2
- package/package.json +3 -2
- package/skills/app-structure/SKILL.md +12 -4
- package/src/choices.ts +3 -0
- package/src/config.ts +2 -0
- package/src/db.ts +1 -1
- package/src/index.ts +437 -1
- package/src/inspect.ts +1247 -154
- package/src/make/inbox.ts +5 -3
- package/src/make/payments.ts +58 -29
- package/src/make/shared.ts +78 -11
- package/src/make/tenancy.ts +6 -4
- package/src/make.ts +172 -54
- package/src/outbox.ts +363 -0
- package/src/preflight.ts +1 -1
- package/src/provider-add.ts +47 -0
- package/src/task.ts +2 -3
- package/src/templates/agents.ts +6 -3
- package/src/templates/base.ts +3 -3
- package/src/templates/index.ts +2 -0
- package/src/templates/server.ts +6 -1
- package/src/templates/shared.ts +13 -1
- package/src/templates/testing.ts +545 -0
- package/src/templates/todos.ts +2 -2
- 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": `
|
|
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
|
-
|
|
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
|
|
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 ?
|
|
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
|
|
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
|
|
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
|
|
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 ?
|
|
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
|
|
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
|
-
|
|
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 ?
|
|
2443
|
-
${mode === "resource" ? ` findById(id: string${options.tenant ?
|
|
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 !==
|
|
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 !==
|
|
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 ===
|
|
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
|
|
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 ? ",
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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:
|
|
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:
|
|
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 ? ",
|
|
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 ? ",
|
|
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 ? ",
|
|
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 ? ",
|
|
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,
|
|
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,7 +3151,7 @@ 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 "
|
|
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";
|
|
@@ -3235,7 +3309,7 @@ export const ${names.singularCamel}Events = [
|
|
|
3235
3309
|
`;
|
|
3236
3310
|
}
|
|
3237
3311
|
function resourcePolicyTestFile(names, options) {
|
|
3238
|
-
return `import { describe, expect, it } from "
|
|
3312
|
+
return `import { describe, expect, it } from "@/lib/beignet-test";
|
|
3239
3313
|
import { createPolicyTester } from "@beignet/core/ports/testing";
|
|
3240
3314
|
import { ${names.singularCamel}Policy } from "../policy";
|
|
3241
3315
|
import type { ${names.singularPascal} } from "../schemas";
|
|
@@ -3762,7 +3836,7 @@ 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 "
|
|
3839
|
+
return `import { describe, expect, it } from "@/lib/beignet-test";
|
|
3766
3840
|
import { createUseCaseTester } from "@beignet/core/application";
|
|
3767
3841
|
${mode === "resource" ? `import { isAppError } from "@beignet/core/errors";\n` : ""}import { defineRoutes } from "@beignet/web";
|
|
3768
3842
|
import { createTestApp } from "@beignet/web/testing";
|