@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.
- package/CHANGELOG.md +107 -0
- package/README.md +70 -17
- package/dist/check.d.ts +2 -0
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +16 -0
- package/dist/check.js.map +1 -1
- package/dist/choices.d.ts +22 -2
- package/dist/choices.d.ts.map +1 -1
- package/dist/choices.js +62 -0
- package/dist/choices.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -1
- package/dist/create-prompts.d.ts +6 -2
- package/dist/create-prompts.d.ts.map +1 -1
- package/dist/create-prompts.js +1 -1
- package/dist/create-prompts.js.map +1 -1
- package/dist/db.d.ts +5 -5
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +5 -5
- package/dist/db.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +386 -27
- package/dist/index.js.map +1 -1
- package/dist/inspect.d.ts +5 -0
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +805 -62
- 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 +12 -3
- package/dist/make/shared.d.ts.map +1 -1
- package/dist/make/shared.js +52 -11
- 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 +136 -65
- 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.d.ts +90 -0
- package/dist/preflight.d.ts.map +1 -0
- package/dist/preflight.js +423 -0
- package/dist/preflight.js.map +1 -0
- package/dist/provider-add.d.ts +10 -1
- package/dist/provider-add.d.ts.map +1 -1
- package/dist/provider-add.js +255 -16
- 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.d.ts.map +1 -1
- package/dist/templates/base.js +5 -11
- 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 +10 -5
- package/dist/templates/server.js.map +1 -1
- package/dist/templates/shared.d.ts +5 -1
- package/dist/templates/shared.d.ts.map +1 -1
- package/dist/templates/shared.js +13 -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/check.ts +23 -0
- package/src/choices.ts +84 -0
- package/src/config.ts +4 -0
- package/src/create-prompts.ts +8 -2
- package/src/db.ts +12 -12
- package/src/index.ts +592 -37
- package/src/inspect.ts +1253 -156
- package/src/make/inbox.ts +5 -3
- package/src/make/payments.ts +58 -29
- package/src/make/shared.ts +87 -13
- package/src/make/tenancy.ts +6 -4
- package/src/make.ts +179 -65
- package/src/outbox.ts +363 -0
- package/src/preflight.ts +596 -0
- package/src/provider-add.ts +270 -16
- package/src/task.ts +2 -3
- package/src/templates/agents.ts +6 -3
- package/src/templates/base.ts +5 -13
- package/src/templates/index.ts +2 -0
- package/src/templates/server.ts +10 -5
- package/src/templates/shared.ts +15 -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;
|
|
@@ -1180,7 +1255,10 @@ export async function makeUpload(options) {
|
|
|
1180
1255
|
force: Boolean(options.force),
|
|
1181
1256
|
dryRun: Boolean(options.dryRun),
|
|
1182
1257
|
name: `${names.feature.kebab}/${names.artifact.kebab}`,
|
|
1183
|
-
files:
|
|
1258
|
+
files: [
|
|
1259
|
+
...(await missingCapabilityBuilderFiles(targetDir, "uploads", config)),
|
|
1260
|
+
...uploadFiles(names, config),
|
|
1261
|
+
],
|
|
1184
1262
|
index: featureArtifactIndexFile("upload", names, config),
|
|
1185
1263
|
dependencies: {
|
|
1186
1264
|
"@beignet/core": beignetDependencyVersion,
|
|
@@ -1581,6 +1659,7 @@ function resourceFiles(names, config, persistence = "memory", mode = "feature",
|
|
|
1581
1659
|
const infraDir = infrastructureDir(config);
|
|
1582
1660
|
const featureDir = resourceFeatureDir(names, config);
|
|
1583
1661
|
const files = [
|
|
1662
|
+
...testSupportTemplateFiles,
|
|
1584
1663
|
{
|
|
1585
1664
|
path: resourceContractFilePath(names, config),
|
|
1586
1665
|
content: contractFile(names, config, mode, options),
|
|
@@ -1706,6 +1785,7 @@ function useCaseFiles(names, config) {
|
|
|
1706
1785
|
}
|
|
1707
1786
|
function testFiles(names, config) {
|
|
1708
1787
|
return [
|
|
1788
|
+
...testSupportTemplateFiles,
|
|
1709
1789
|
{
|
|
1710
1790
|
path: useCaseTestFilePath(names, config),
|
|
1711
1791
|
content: useCaseTestFile(names, config),
|
|
@@ -2258,7 +2338,7 @@ function listUseCaseFile(names, config, options) {
|
|
|
2258
2338
|
const filePath = path.join(resourceUseCaseDir(names, config), `list-${names.pluralKebab}.ts`);
|
|
2259
2339
|
return `import "@beignet/core/server-only";
|
|
2260
2340
|
import { normalizeCursorPage } from "@beignet/core/pagination";
|
|
2261
|
-
import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
|
|
2341
|
+
${options.tenant ? `import { requireTenantScope } from "@beignet/core/tenancy";\n` : ""}import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
|
|
2262
2342
|
import {
|
|
2263
2343
|
decode${names.singularPascal}Cursor,
|
|
2264
2344
|
List${names.pluralPascal}InputSchema,
|
|
@@ -2270,7 +2350,7 @@ export const list${names.pluralPascal}UseCase = useCase
|
|
|
2270
2350
|
.input(List${names.pluralPascal}InputSchema)
|
|
2271
2351
|
.output(List${names.pluralPascal}OutputSchema)
|
|
2272
2352
|
.run(async ({ ctx, input }) => {
|
|
2273
|
-
${options.tenant ? `\t\tconst
|
|
2353
|
+
${options.tenant ? `\t\tconst scope = requireTenantScope(ctx);\n\n` : ""} const page = normalizeCursorPage(input, {
|
|
2274
2354
|
defaultLimit: 20,
|
|
2275
2355
|
maxLimit: 100,
|
|
2276
2356
|
});
|
|
@@ -2281,14 +2361,14 @@ ${options.tenant ? `\t\tconst tenantId = ctx.tenant?.id;\n\t\tif (!tenantId) {\n
|
|
|
2281
2361
|
name: input.name,
|
|
2282
2362
|
sortBy: input.sortBy,
|
|
2283
2363
|
sortDirection: input.sortDirection,
|
|
2284
|
-
${options.tenant ?
|
|
2364
|
+
}${options.tenant ? ", scope" : ""});
|
|
2285
2365
|
});
|
|
2286
2366
|
`;
|
|
2287
2367
|
}
|
|
2288
2368
|
function createUseCaseFile(names, config, options) {
|
|
2289
2369
|
const filePath = path.join(resourceUseCaseDir(names, config), `create-${names.singularKebab}.ts`);
|
|
2290
2370
|
return `import "@beignet/core/server-only";
|
|
2291
|
-
import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
|
|
2371
|
+
${options.tenant ? `import { requireTenantScope } from "@beignet/core/tenancy";\n` : ""}import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
|
|
2292
2372
|
import {
|
|
2293
2373
|
Create${names.singularPascal}InputSchema,
|
|
2294
2374
|
${names.singularPascal}Schema,
|
|
@@ -2300,9 +2380,7 @@ export const create${names.singularPascal}UseCase = useCase
|
|
|
2300
2380
|
.input(Create${names.singularPascal}InputSchema)
|
|
2301
2381
|
.output(${names.singularPascal}Schema)
|
|
2302
2382
|
.run(async ({ ctx, input }) => {
|
|
2303
|
-
${options.tenant ? `\t\tconst
|
|
2304
|
-
...input,
|
|
2305
|
-
${options.tenant ? `\t\t\ttenantId,\n` : ""} });
|
|
2383
|
+
${options.tenant ? `\t\tconst scope = requireTenantScope(ctx);\n\n` : ""}${options.auth ? `\t\tawait ctx.gate.authorize("${names.pluralCamel}.create");\n\n` : ""} const ${names.singularCamel} = await ctx.ports.${names.pluralCamel}.create(input${options.tenant ? ", scope" : ""});
|
|
2306
2384
|
${options.events ? `\n\t\tawait ctx.ports.eventBus.publish(${names.singularPascal}Created, {\n\t\t\tid: ${names.singularCamel}.id,\n\t\t});\n` : ""}
|
|
2307
2385
|
return ${names.singularCamel};
|
|
2308
2386
|
});
|
|
@@ -2311,7 +2389,7 @@ ${options.events ? `\n\t\tawait ctx.ports.eventBus.publish(${names.singularPasca
|
|
|
2311
2389
|
function getUseCaseFile(names, config, options) {
|
|
2312
2390
|
const filePath = path.join(resourceUseCaseDir(names, config), `get-${names.singularKebab}.ts`);
|
|
2313
2391
|
return `import "@beignet/core/server-only";
|
|
2314
|
-
import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
|
|
2392
|
+
${options.tenant ? `import { requireTenantScope } from "@beignet/core/tenancy";\n` : ""}import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
|
|
2315
2393
|
import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
|
|
2316
2394
|
import {
|
|
2317
2395
|
${names.singularPascal}IdInputSchema,
|
|
@@ -2323,7 +2401,7 @@ export const get${names.singularPascal}UseCase = useCase
|
|
|
2323
2401
|
.input(${names.singularPascal}IdInputSchema)
|
|
2324
2402
|
.output(${names.singularPascal}Schema)
|
|
2325
2403
|
.run(async ({ ctx, input }) => {
|
|
2326
|
-
${options.tenant ? `\t\tconst
|
|
2404
|
+
${options.tenant ? `\t\tconst scope = requireTenantScope(ctx);\n\n` : ""} const ${names.singularCamel} = await ctx.ports.${names.pluralCamel}.findById(input.id${options.tenant ? ", scope" : ""});
|
|
2327
2405
|
if (!${names.singularCamel}) {
|
|
2328
2406
|
throw appError("${names.singularPascal}NotFound", {
|
|
2329
2407
|
details: { id: input.id },
|
|
@@ -2337,7 +2415,7 @@ ${options.tenant ? `\t\tconst tenantId = ctx.tenant?.id;\n\t\tif (!tenantId) {\n
|
|
|
2337
2415
|
function updateUseCaseFile(names, config, options) {
|
|
2338
2416
|
const filePath = path.join(resourceUseCaseDir(names, config), `update-${names.singularKebab}.ts`);
|
|
2339
2417
|
return `import "@beignet/core/server-only";
|
|
2340
|
-
import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
|
|
2418
|
+
${options.tenant ? `import { requireTenantScope } from "@beignet/core/tenancy";\n` : ""}import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
|
|
2341
2419
|
import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
|
|
2342
2420
|
${options.events ? `import { ${names.singularPascal}Updated } from "../domain/events";\n` : ""}import {
|
|
2343
2421
|
Update${names.singularPascal}InputSchema,
|
|
@@ -2349,7 +2427,7 @@ export const update${names.singularPascal}UseCase = useCase
|
|
|
2349
2427
|
.input(Update${names.singularPascal}InputSchema)
|
|
2350
2428
|
.output(${names.singularPascal}Schema)
|
|
2351
2429
|
.run(async ({ ctx, input }) => {
|
|
2352
|
-
${options.tenant ? `\t\tconst
|
|
2430
|
+
${options.tenant ? `\t\tconst scope = requireTenantScope(ctx);\n\n` : ""} const existing = await ctx.ports.${names.pluralCamel}.findById(input.id${options.tenant ? ", scope" : ""});
|
|
2353
2431
|
if (!existing) {
|
|
2354
2432
|
throw appError("${names.singularPascal}NotFound", {
|
|
2355
2433
|
details: { id: input.id },
|
|
@@ -2363,7 +2441,7 @@ ${options.auth ? `\t\tawait ctx.gate.authorize("${names.pluralCamel}.update", ex
|
|
|
2363
2441
|
|
|
2364
2442
|
const ${names.singularCamel} = await ctx.ports.${names.pluralCamel}.update({
|
|
2365
2443
|
...input,
|
|
2366
|
-
${options.tenant ?
|
|
2444
|
+
}${options.tenant ? ", scope" : ""});
|
|
2367
2445
|
if (!${names.singularCamel}) {
|
|
2368
2446
|
throw appError("${names.singularPascal}Conflict", {
|
|
2369
2447
|
details: { id: input.id, expectedVersion: existing.version },
|
|
@@ -2378,7 +2456,7 @@ function deleteUseCaseFile(names, config, options) {
|
|
|
2378
2456
|
const filePath = path.join(resourceUseCaseDir(names, config), `delete-${names.singularKebab}.ts`);
|
|
2379
2457
|
return `import "@beignet/core/server-only";
|
|
2380
2458
|
import { z } from "zod";
|
|
2381
|
-
import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
|
|
2459
|
+
${options.tenant ? `import { requireTenantScope } from "@beignet/core/tenancy";\n` : ""}import { appError } from "${relativeModule(filePath, resourceSharedErrorsPath(config))}";
|
|
2382
2460
|
import { useCase } from "${relativeModule(filePath, config.paths.useCaseBuilder)}";
|
|
2383
2461
|
${options.events ? `import { ${names.singularPascal}Deleted } from "../domain/events";\n` : ""}import { ${names.singularPascal}IdInputSchema } from "../schemas";
|
|
2384
2462
|
|
|
@@ -2387,7 +2465,7 @@ export const delete${names.singularPascal}UseCase = useCase
|
|
|
2387
2465
|
.input(${names.singularPascal}IdInputSchema)
|
|
2388
2466
|
.output(z.void())
|
|
2389
2467
|
.run(async ({ ctx, input }) => {
|
|
2390
|
-
${options.tenant ? `\t\tconst
|
|
2468
|
+
${options.tenant ? `\t\tconst scope = requireTenantScope(ctx);\n\n` : ""}${options.auth ? `\t\tconst existing = await ctx.ports.${names.pluralCamel}.findById(input.id${options.tenant ? ", scope" : ""});\n\t\tif (!existing) {\n\t\t\tthrow appError("${names.singularPascal}NotFound", {\n\t\t\t\tdetails: { id: input.id },\n\t\t\t});\n\t\t}\n\t\tawait ctx.gate.authorize("${names.pluralCamel}.delete", existing);\n\n` : ""} const deleted = await ctx.ports.${names.pluralCamel}.delete(input.id${options.tenant ? ", scope" : ""});
|
|
2391
2469
|
if (!deleted) {
|
|
2392
2470
|
throw appError("${names.singularPascal}NotFound", {
|
|
2393
2471
|
details: { id: input.id },
|
|
@@ -2417,7 +2495,7 @@ function repositoryPortFile(names, config, mode, options) {
|
|
|
2417
2495
|
PageResult,
|
|
2418
2496
|
SortDirection,
|
|
2419
2497
|
} from "@beignet/core/pagination";
|
|
2420
|
-
import type {
|
|
2498
|
+
${options.tenant ? `import type { TenantScope } from "@beignet/core/tenancy";\n` : ""}import type {
|
|
2421
2499
|
Create${names.singularPascal}Input,
|
|
2422
2500
|
${names.singularPascal}Cursor,
|
|
2423
2501
|
${names.singularPascal}SortBy,
|
|
@@ -2431,13 +2509,12 @@ export type List${names.pluralPascal}Query = {
|
|
|
2431
2509
|
name?: string;
|
|
2432
2510
|
sortBy: ${names.singularPascal}SortBy;
|
|
2433
2511
|
sortDirection: SortDirection;
|
|
2434
|
-
|
|
2435
|
-
${options.tenant ? `export type ${names.singularPascal}TenantFilter = { tenantId: string };\nexport type Create${names.singularPascal}RepositoryInput = Create${names.singularPascal}Input & ${names.singularPascal}TenantFilter;\n${mode === "resource" ? `export type Update${names.singularPascal}RepositoryInput = Update${names.singularPascal}Input & ${names.singularPascal}TenantFilter;\n` : ""}` : ""}
|
|
2512
|
+
};
|
|
2436
2513
|
|
|
2437
2514
|
export interface ${names.singularPascal}Repository {
|
|
2438
|
-
list(query: List${names.pluralPascal}Query): Promise<List${names.pluralPascal}Result>;
|
|
2439
|
-
create(input: ${options.tenant ?
|
|
2440
|
-
${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` : ""}}
|
|
2441
2518
|
`;
|
|
2442
2519
|
}
|
|
2443
2520
|
function inMemoryRepositoryFile(names, config, mode, options) {
|
|
@@ -2465,13 +2542,13 @@ function inMemoryRepositoryFile(names, config, mode, options) {
|
|
|
2465
2542
|
? " || existing.deletedAt !== null"
|
|
2466
2543
|
: "";
|
|
2467
2544
|
const deleteImplementation = options.softDelete
|
|
2468
|
-
? `${options.tenant ? `\t\t\tconst existing = ${names.pluralCamel}.get(id);\n\t\t\tif (!existing || existing.tenantId !==
|
|
2545
|
+
? `${options.tenant ? `\t\t\tconst existing = ${names.pluralCamel}.get(id);\n\t\t\tif (!existing || existing.tenantId !== tenantId || existing.deletedAt !== null) return false;\n` : `\t\t\tconst existing = ${names.pluralCamel}.get(id);\n\t\t\tif (!existing || existing.deletedAt !== null) return false;\n`} const now = new Date().toISOString();
|
|
2469
2546
|
${names.pluralCamel}.set(id, { ...existing, deletedAt: now, updatedAt: now });
|
|
2470
2547
|
return true;`
|
|
2471
|
-
: `${options.tenant ? `\t\t\tconst existing = ${names.pluralCamel}.get(id);\n\t\t\tif (existing?.tenantId !==
|
|
2548
|
+
: `${options.tenant ? `\t\t\tconst existing = ${names.pluralCamel}.get(id);\n\t\t\tif (existing?.tenantId !== tenantId) return false;\n` : ""} return ${names.pluralCamel}.delete(id);`;
|
|
2472
2549
|
return `import "@beignet/core/server-only";
|
|
2473
2550
|
import { cursorPageResult } from "@beignet/core/pagination";
|
|
2474
|
-
import type { ${names.singularPascal}Repository } from "${aliasModule(repositoryPortPath)}";
|
|
2551
|
+
${options.tenant ? `import { tenantScopeId } from "@beignet/core/tenancy";\n` : ""}import type { ${names.singularPascal}Repository } from "${aliasModule(repositoryPortPath)}";
|
|
2475
2552
|
import { encode${names.singularPascal}Cursor } from "${aliasModule(resourceSchemaFilePath(names, config))}";
|
|
2476
2553
|
import type {
|
|
2477
2554
|
Create${names.singularPascal}Input,
|
|
@@ -2539,10 +2616,10 @@ export function createInMemory${names.singularPascal}Repository(
|
|
|
2539
2616
|
);
|
|
2540
2617
|
|
|
2541
2618
|
return {
|
|
2542
|
-
async list(query) {
|
|
2619
|
+
async list(query${options.tenant ? ", scope" : ""}) {
|
|
2543
2620
|
const name = query.name?.toLocaleLowerCase();
|
|
2544
|
-
const all${names.pluralPascal} = Array.from(${names.pluralCamel}.values())
|
|
2545
|
-
${activeFilter}${options.tenant ? `\t\t\t\t.filter((${names.singularCamel}) => ${names.singularCamel}.tenantId ===
|
|
2621
|
+
${options.tenant ? `\t\t\tconst tenantId = tenantScopeId(scope);\n` : ""} const all${names.pluralPascal} = Array.from(${names.pluralCamel}.values())
|
|
2622
|
+
${activeFilter}${options.tenant ? `\t\t\t\t.filter((${names.singularCamel}) => ${names.singularCamel}.tenantId === tenantId)\n` : ""} .filter((${names.singularCamel}) => !name || ${names.singularCamel}.name.toLocaleLowerCase().includes(name))
|
|
2546
2623
|
.sort((left, right) => compare${names.pluralPascal}(left, right, query))
|
|
2547
2624
|
.filter((${names.singularCamel}) => isAfter${names.singularPascal}Cursor(${names.singularCamel}, query));
|
|
2548
2625
|
const pageItems = all${names.pluralPascal}.slice(0, query.page.limit);
|
|
@@ -2557,11 +2634,11 @@ ${activeFilter}${options.tenant ? `\t\t\t\t.filter((${names.singularCamel}) => $
|
|
|
2557
2634
|
nextCursor,
|
|
2558
2635
|
);
|
|
2559
2636
|
},
|
|
2560
|
-
async create(input) {
|
|
2637
|
+
async create(input${options.tenant ? ", scope" : ""}) {
|
|
2561
2638
|
const now = new Date().toISOString();
|
|
2562
|
-
const ${names.singularCamel}: ${names.singularPascal} = {
|
|
2639
|
+
${options.tenant ? `\t\t\tconst tenantId = tenantScopeId(scope);\n` : ""} const ${names.singularCamel}: ${names.singularPascal} = {
|
|
2563
2640
|
id: crypto.randomUUID(),
|
|
2564
|
-
${options.tenant ? `\t\t\t\ttenantId
|
|
2641
|
+
${options.tenant ? `\t\t\t\ttenantId,\n` : ""} name: input.name,
|
|
2565
2642
|
version: 1,
|
|
2566
2643
|
createdAt: now,
|
|
2567
2644
|
updatedAt: now,
|
|
@@ -2571,7 +2648,7 @@ ${options.tenant ? `\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input.na
|
|
|
2571
2648
|
${newDeletedAtField} });
|
|
2572
2649
|
return ${names.singularCamel};
|
|
2573
2650
|
},
|
|
2574
|
-
${mode === "resource" ? ` async findById(id: string${options.tenant ? ",
|
|
2651
|
+
${mode === "resource" ? ` async findById(id: string${options.tenant ? ", scope" : ""}) {\n${options.tenant ? `\t\t\tconst tenantId = tenantScopeId(scope);\n` : ""} const ${names.singularCamel} = ${names.pluralCamel}.get(id) ?? null;\n${options.tenant ? `\t\t\tif (${names.singularCamel}?.tenantId !== tenantId) return null;\n` : ""}${findDeletedGuard} return ${names.singularCamel} ? to${names.singularPascal}(${names.singularCamel}) : null;\n },\n async update(input${options.tenant ? ", scope" : ""}) {\n${options.tenant ? `\t\t\tconst tenantId = tenantScopeId(scope);\n` : ""} const existing = ${names.pluralCamel}.get(input.id);\n if (!existing${options.tenant ? " || existing.tenantId !== tenantId" : ""}${updateDeletedGuard} || existing.version !== input.version) return null;\n\n const next: ${mapValueType} = {\n ...existing,\n name: input.name,\n version: existing.version + 1,\n updatedAt: new Date().toISOString(),\n };\n ${names.pluralCamel}.set(input.id, next);\n return to${names.singularPascal}(next);\n },\n async delete(id: string${options.tenant ? ", scope" : ""}) {\n${options.tenant ? `\t\t\tconst tenantId = tenantScopeId(scope);\n` : ""}${deleteImplementation}\n },\n` : ""}
|
|
2575
2652
|
};
|
|
2576
2653
|
}
|
|
2577
2654
|
`;
|
|
@@ -2677,20 +2754,20 @@ function drizzleRepositoryFile(names, config, mode, options, dialect) {
|
|
|
2677
2754
|
const findWhere = drizzleResourceWhere(names, options, [
|
|
2678
2755
|
`eq(schema.${names.pluralCamel}.id, id)`,
|
|
2679
2756
|
...(options.tenant
|
|
2680
|
-
? [`eq(schema.${names.pluralCamel}.tenantId,
|
|
2757
|
+
? [`eq(schema.${names.pluralCamel}.tenantId, tenantScopeId(scope))`]
|
|
2681
2758
|
: []),
|
|
2682
2759
|
]);
|
|
2683
2760
|
const updateWhere = drizzleResourceWhere(names, options, [
|
|
2684
2761
|
`eq(schema.${names.pluralCamel}.id, input.id)`,
|
|
2685
2762
|
`eq(schema.${names.pluralCamel}.version, input.version)`,
|
|
2686
2763
|
...(options.tenant
|
|
2687
|
-
? [`eq(schema.${names.pluralCamel}.tenantId,
|
|
2764
|
+
? [`eq(schema.${names.pluralCamel}.tenantId, tenantScopeId(scope))`]
|
|
2688
2765
|
: []),
|
|
2689
2766
|
]);
|
|
2690
2767
|
const deleteWhere = drizzleResourceWhere(names, options, [
|
|
2691
2768
|
`eq(schema.${names.pluralCamel}.id, id)`,
|
|
2692
2769
|
...(options.tenant
|
|
2693
|
-
? [`eq(schema.${names.pluralCamel}.tenantId,
|
|
2770
|
+
? [`eq(schema.${names.pluralCamel}.tenantId, tenantScopeId(scope))`]
|
|
2694
2771
|
: []),
|
|
2695
2772
|
]);
|
|
2696
2773
|
const drizzleImports = [
|
|
@@ -2708,7 +2785,7 @@ function drizzleRepositoryFile(names, config, mode, options, dialect) {
|
|
|
2708
2785
|
// After a version-guarded update succeeds, re-select by identity only —
|
|
2709
2786
|
// matching what `.returning()` yields on the dialects that support it.
|
|
2710
2787
|
const updateReselectWhere = options.tenant
|
|
2711
|
-
? `and(eq(schema.${names.pluralCamel}.id, input.id), eq(schema.${names.pluralCamel}.tenantId,
|
|
2788
|
+
? `and(eq(schema.${names.pluralCamel}.id, input.id), eq(schema.${names.pluralCamel}.tenantId, tenantScopeId(scope)))`
|
|
2712
2789
|
: `eq(schema.${names.pluralCamel}.id, input.id)`;
|
|
2713
2790
|
// drizzle-orm/mysql2 has no `.returning(...)`; mutations resolve to a
|
|
2714
2791
|
// `[ResultSetHeader, FieldPacket[]]` tuple typed as `unknown` through the
|
|
@@ -2729,13 +2806,13 @@ function affectedRows(result: unknown): number {
|
|
|
2729
2806
|
`
|
|
2730
2807
|
: "";
|
|
2731
2808
|
const createMethod = dialect.supportsReturning
|
|
2732
|
-
? ` async create(input) {
|
|
2809
|
+
? ` async create(input${options.tenant ? ", scope" : ""}) {
|
|
2733
2810
|
const now = new Date().toISOString();
|
|
2734
2811
|
const [row] = await db
|
|
2735
2812
|
.insert(schema.${names.pluralCamel})
|
|
2736
2813
|
.values({
|
|
2737
2814
|
id: crypto.randomUUID(),
|
|
2738
|
-
${options.tenant ? `\t\t\t\t\ttenantId:
|
|
2815
|
+
${options.tenant ? `\t\t\t\t\ttenantId: tenantScopeId(scope),\n` : ""} name: input.name,
|
|
2739
2816
|
version: 1,
|
|
2740
2817
|
createdAt: now,
|
|
2741
2818
|
updatedAt: now,
|
|
@@ -2749,11 +2826,11 @@ ${options.tenant ? `\t\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input
|
|
|
2749
2826
|
return to${names.singularPascal}(row);
|
|
2750
2827
|
},
|
|
2751
2828
|
`
|
|
2752
|
-
: ` async create(input) {
|
|
2829
|
+
: ` async create(input${options.tenant ? ", scope" : ""}) {
|
|
2753
2830
|
const now = new Date().toISOString();
|
|
2754
2831
|
const ${names.singularCamel}: ${names.singularPascal} = {
|
|
2755
2832
|
id: crypto.randomUUID(),
|
|
2756
|
-
${options.tenant ? `\t\t\t\ttenantId:
|
|
2833
|
+
${options.tenant ? `\t\t\t\ttenantId: tenantScopeId(scope),\n` : ""} name: input.name,
|
|
2757
2834
|
version: 1,
|
|
2758
2835
|
createdAt: now,
|
|
2759
2836
|
updatedAt: now,
|
|
@@ -2766,7 +2843,7 @@ ${options.tenant ? `\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input.na
|
|
|
2766
2843
|
const resourceMethods = mode !== "resource"
|
|
2767
2844
|
? ""
|
|
2768
2845
|
: dialect.supportsReturning
|
|
2769
|
-
? ` async findById(id: string${options.tenant ? ",
|
|
2846
|
+
? ` async findById(id: string${options.tenant ? ", scope" : ""}) {
|
|
2770
2847
|
const [row] = await db
|
|
2771
2848
|
.select()
|
|
2772
2849
|
.from(schema.${names.pluralCamel})
|
|
@@ -2775,7 +2852,7 @@ ${options.tenant ? `\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input.na
|
|
|
2775
2852
|
|
|
2776
2853
|
return row ? to${names.singularPascal}(row) : null;
|
|
2777
2854
|
},
|
|
2778
|
-
async update(input) {
|
|
2855
|
+
async update(input${options.tenant ? ", scope" : ""}) {
|
|
2779
2856
|
const [row] = await db
|
|
2780
2857
|
.update(schema.${names.pluralCamel})
|
|
2781
2858
|
.set({
|
|
@@ -2788,12 +2865,12 @@ ${options.tenant ? `\t\t\t\ttenantId: input.tenantId,\n` : ""} name: input.na
|
|
|
2788
2865
|
|
|
2789
2866
|
return row ? to${names.singularPascal}(row) : null;
|
|
2790
2867
|
},
|
|
2791
|
-
async delete(id: string${options.tenant ? ",
|
|
2868
|
+
async delete(id: string${options.tenant ? ", scope" : ""}) {
|
|
2792
2869
|
${options.softDelete ? `\t\t\tconst now = new Date().toISOString();\n\t\t\tconst rows = await db\n\t\t\t\t.update(schema.${names.pluralCamel})\n\t\t\t\t.set({ deletedAt: now, updatedAt: now })\n\t\t\t\t.where(${deleteWhere})\n\t\t\t\t.returning({ id: schema.${names.pluralCamel}.id });\n` : `\t\t\tconst rows = await db\n\t\t\t\t.delete(schema.${names.pluralCamel})\n\t\t\t\t.where(${deleteWhere})\n\t\t\t\t.returning({ id: schema.${names.pluralCamel}.id });\n`}
|
|
2793
2870
|
return rows.length > 0;
|
|
2794
2871
|
},
|
|
2795
2872
|
`
|
|
2796
|
-
: ` async findById(id: string${options.tenant ? ",
|
|
2873
|
+
: ` async findById(id: string${options.tenant ? ", scope" : ""}) {
|
|
2797
2874
|
const [row] = await db
|
|
2798
2875
|
.select()
|
|
2799
2876
|
.from(schema.${names.pluralCamel})
|
|
@@ -2802,7 +2879,7 @@ ${options.softDelete ? `\t\t\tconst now = new Date().toISOString();\n\t\t\tconst
|
|
|
2802
2879
|
|
|
2803
2880
|
return row ? to${names.singularPascal}(row) : null;
|
|
2804
2881
|
},
|
|
2805
|
-
async update(input) {
|
|
2882
|
+
async update(input${options.tenant ? ", scope" : ""}) {
|
|
2806
2883
|
const result = await db
|
|
2807
2884
|
.update(schema.${names.pluralCamel})
|
|
2808
2885
|
.set({
|
|
@@ -2822,14 +2899,14 @@ ${options.softDelete ? `\t\t\tconst now = new Date().toISOString();\n\t\t\tconst
|
|
|
2822
2899
|
|
|
2823
2900
|
return row ? to${names.singularPascal}(row) : null;
|
|
2824
2901
|
},
|
|
2825
|
-
async delete(id: string${options.tenant ? ",
|
|
2902
|
+
async delete(id: string${options.tenant ? ", scope" : ""}) {
|
|
2826
2903
|
${options.softDelete ? `\t\t\tconst now = new Date().toISOString();\n\t\t\tconst result = await db\n\t\t\t\t.update(schema.${names.pluralCamel})\n\t\t\t\t.set({ deletedAt: now, updatedAt: now })\n\t\t\t\t.where(${deleteWhere});\n` : `\t\t\tconst result = await db\n\t\t\t\t.delete(schema.${names.pluralCamel})\n\t\t\t\t.where(${deleteWhere});\n`}
|
|
2827
2904
|
return affectedRows(result) > 0;
|
|
2828
2905
|
},
|
|
2829
2906
|
`;
|
|
2830
2907
|
return `import "@beignet/core/server-only";
|
|
2831
2908
|
import { cursorPageResult } from "@beignet/core/pagination";
|
|
2832
|
-
import type { ${dialect.dbTypeName} } from "@beignet/provider-db-drizzle/${dialect.subpath}";
|
|
2909
|
+
${options.tenant ? `import { tenantScopeId } from "@beignet/core/tenancy";\n` : ""}import type { ${dialect.dbTypeName} } from "@beignet/provider-db-drizzle/${dialect.subpath}";
|
|
2833
2910
|
import { ${drizzleImports.join(", ")} } from "drizzle-orm";
|
|
2834
2911
|
import type { ${names.singularPascal}Repository } from "${aliasModule(repositoryPortPath)}";
|
|
2835
2912
|
import { encode${names.singularPascal}Cursor } from "${aliasModule(resourceSchemaFilePath(names, config))}";
|
|
@@ -2876,9 +2953,9 @@ function ${names.singularCamel}CursorFilter(
|
|
|
2876
2953
|
|
|
2877
2954
|
function ${names.singularCamel}ListWhere(
|
|
2878
2955
|
query: List${names.pluralPascal}Query,
|
|
2879
|
-
): SQL<unknown> | undefined {
|
|
2956
|
+
${options.tenant ? `\tscope: Parameters<${names.singularPascal}Repository["list"]>[1],\n` : ""}): SQL<unknown> | undefined {
|
|
2880
2957
|
const filters: SQL<unknown>[] = [
|
|
2881
|
-
${options.tenant ? `\t\teq(schema.${names.pluralCamel}.tenantId,
|
|
2958
|
+
${options.tenant ? `\t\teq(schema.${names.pluralCamel}.tenantId, tenantScopeId(scope)),\n` : ""}${options.softDelete ? `\t\tisNull(schema.${names.pluralCamel}.deletedAt),\n` : ""} ];
|
|
2882
2959
|
const cursor = ${names.singularCamel}CursorFilter(query);
|
|
2883
2960
|
|
|
2884
2961
|
if (query.name) {
|
|
@@ -2912,8 +2989,8 @@ export function createDrizzle${names.singularPascal}Repository(
|
|
|
2912
2989
|
db: ${dialect.dbTypeName}<typeof schema>,
|
|
2913
2990
|
): ${names.singularPascal}Repository {
|
|
2914
2991
|
return {
|
|
2915
|
-
async list(query) {
|
|
2916
|
-
const where = ${names.singularCamel}ListWhere(query);
|
|
2992
|
+
async list(query${options.tenant ? ", scope" : ""}) {
|
|
2993
|
+
const where = ${names.singularCamel}ListWhere(query${options.tenant ? ", scope" : ""});
|
|
2917
2994
|
const rows = await db
|
|
2918
2995
|
.select()
|
|
2919
2996
|
.from(schema.${names.pluralCamel})
|
|
@@ -3074,7 +3151,7 @@ export const ${names.exportName} = useCase
|
|
|
3074
3151
|
}
|
|
3075
3152
|
function useCaseTestFile(names, config) {
|
|
3076
3153
|
const filePath = useCaseTestFilePath(names, config);
|
|
3077
|
-
return `import { describe, expect, it } from "
|
|
3154
|
+
return `import { describe, expect, it } from "@/lib/beignet-test";
|
|
3078
3155
|
import { createUseCaseTester } from "@beignet/core/application";
|
|
3079
3156
|
import { createInMemoryDevtools } from "@beignet/devtools";
|
|
3080
3157
|
import { createTestContextFactory, createTestPorts } from "@beignet/core/testing";
|
|
@@ -3232,7 +3309,7 @@ export const ${names.singularCamel}Events = [
|
|
|
3232
3309
|
`;
|
|
3233
3310
|
}
|
|
3234
3311
|
function resourcePolicyTestFile(names, options) {
|
|
3235
|
-
return `import { describe, expect, it } from "
|
|
3312
|
+
return `import { describe, expect, it } from "@/lib/beignet-test";
|
|
3236
3313
|
import { createPolicyTester } from "@beignet/core/ports/testing";
|
|
3237
3314
|
import { ${names.singularCamel}Policy } from "../policy";
|
|
3238
3315
|
import type { ${names.singularPascal} } from "../schemas";
|
|
@@ -3459,9 +3536,8 @@ ${names.timezone ? `\t\ttimezone: "${names.timezone}",\n` : ""}\t\tpayload: ${na
|
|
|
3459
3536
|
`;
|
|
3460
3537
|
}
|
|
3461
3538
|
function uploadFile(names, config) {
|
|
3462
|
-
return `import {
|
|
3463
|
-
import {
|
|
3464
|
-
import type { AppContext } from "${aliasModule(config.paths.appContext)}";
|
|
3539
|
+
return `import { z } from "zod";
|
|
3540
|
+
import { defineUpload } from "${aliasModule(config.paths.uploadsBuilder)}";
|
|
3465
3541
|
|
|
3466
3542
|
export const ${names.metadataSchemaName} = z.object({
|
|
3467
3543
|
\tresourceId: z.string().min(1),
|
|
@@ -3469,12 +3545,7 @@ export const ${names.metadataSchemaName} = z.object({
|
|
|
3469
3545
|
|
|
3470
3546
|
export type ${names.metadataTypeName} = z.infer<typeof ${names.metadataSchemaName}>;
|
|
3471
3547
|
|
|
3472
|
-
export const ${names.uploadExportName} = defineUpload
|
|
3473
|
-
\t"${names.uploadName}",
|
|
3474
|
-
\ttypeof ${names.metadataSchemaName},
|
|
3475
|
-
\tAppContext,
|
|
3476
|
-
\t{ objectKeys: string[] }
|
|
3477
|
-
>("${names.uploadName}", {
|
|
3548
|
+
export const ${names.uploadExportName} = defineUpload("${names.uploadName}", {
|
|
3478
3549
|
\tmetadata: ${names.metadataSchemaName},
|
|
3479
3550
|
\tfile: {
|
|
3480
3551
|
\t\tcontentTypes: ["application/pdf", "text/plain"],
|
|
@@ -3765,7 +3836,7 @@ function testFile(names, config, mode, options) {
|
|
|
3765
3836
|
const repositoryPath = path.join(path.dirname(config.paths.infrastructurePorts), names.pluralKebab, `in-memory-${names.singularKebab}-repository.ts`);
|
|
3766
3837
|
const serverContextPath = path.join(path.dirname(config.paths.server), "context.ts");
|
|
3767
3838
|
const requestTarget = "app";
|
|
3768
|
-
return `import { describe, expect, it } from "
|
|
3839
|
+
return `import { describe, expect, it } from "@/lib/beignet-test";
|
|
3769
3840
|
import { createUseCaseTester } from "@beignet/core/application";
|
|
3770
3841
|
${mode === "resource" ? `import { isAppError } from "@beignet/core/errors";\n` : ""}import { defineRoutes } from "@beignet/web";
|
|
3771
3842
|
import { createTestApp } from "@beignet/web/testing";
|