create-flowdular 0.2.4 → 0.2.6
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/README.md +11 -0
- package/agent-template/.agents/skills/agent-tool-design/SKILL.md +203 -0
- package/agent-template/.agents/skills/auth-security-review/SKILL.md +90 -0
- package/agent-template/.agents/skills/auto-review/SKILL.md +103 -0
- package/agent-template/.agents/skills/bug-hunt/SKILL.md +104 -0
- package/agent-template/.agents/skills/business-agent-design/SKILL.md +182 -0
- package/agent-template/.agents/skills/cli-extension/SKILL.md +108 -0
- package/agent-template/.agents/skills/core-extend/SKILL.md +99 -0
- package/agent-template/.agents/skills/database-adapter/SKILL.md +198 -0
- package/agent-template/.agents/skills/database-adapter/references/first-run-and-matrix.md +105 -0
- package/agent-template/.agents/skills/migration-authoring/SKILL.md +161 -0
- package/agent-template/.agents/skills/module-new/SKILL.md +171 -0
- package/agent-template/.agents/skills/module-update/SKILL.md +91 -0
- package/agent-template/.agents/skills/perf-audit/SKILL.md +98 -0
- package/agent-template/.agents/skills/release-eject-pr/SKILL.md +107 -0
- package/agent-template/.agents/skills/spec-approval/SKILL.md +106 -0
- package/agent-template/.agents/skills/test-hardening/SKILL.md +79 -0
- package/agent-template/.agents/skills/translations-i18n/SKILL.md +78 -0
- package/agent-template/.agents/skills/ux-design/SKILL.md +92 -0
- package/agent-template/.agents/skills/variables/SKILL.md +156 -0
- package/agent-template/.agents/skills/workflow-development/SKILL.md +192 -0
- package/agent-template/.ai/README.md +62 -0
- package/agent-template/.ai/agents/README.md +27 -0
- package/agent-template/.ai/agents/module-executor.md +36 -0
- package/agent-template/.ai/agents/reviewer.md +23 -0
- package/agent-template/.ai/agents/sandbox/agentic-engineer.md +31 -0
- package/agent-template/.ai/agents/sandbox/backend-engineer.md +36 -0
- package/agent-template/.ai/agents/sandbox/business-manager.md +23 -0
- package/agent-template/.ai/agents/sandbox/frontend-engineer.md +27 -0
- package/agent-template/.ai/agents/sandbox/ux-designer.md +23 -0
- package/agent-template/.ai/agents/spec-author.md +29 -0
- package/agent-template/.ai/blueprints/add-migration/README.md +5 -0
- package/agent-template/.ai/blueprints/add-migration/allowed-paths.yaml +23 -0
- package/agent-template/.ai/blueprints/add-migration/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/add-migration/examples/invalid/input-destructive.json +6 -0
- package/agent-template/.ai/blueprints/add-migration/examples/invalid/plan-unnumbered-file.json +9 -0
- package/agent-template/.ai/blueprints/add-migration/examples/valid/input.json +6 -0
- package/agent-template/.ai/blueprints/add-migration/examples/valid/plan.json +9 -0
- package/agent-template/.ai/blueprints/add-migration/gates.yaml +30 -0
- package/agent-template/.ai/blueprints/add-migration/input.schema.json +23 -0
- package/agent-template/.ai/blueprints/add-migration/plan.schema.json +54 -0
- package/agent-template/.ai/blueprints/add-migration/required-files.yaml +18 -0
- package/agent-template/.ai/blueprints/add-migration/spec-requirements.yaml +13 -0
- package/agent-template/.ai/blueprints/add-migration/steps.yaml +62 -0
- package/agent-template/.ai/blueprints/author-spec/README.md +5 -0
- package/agent-template/.ai/blueprints/author-spec/allowed-paths.yaml +7 -0
- package/agent-template/.ai/blueprints/author-spec/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/author-spec/examples/invalid/input-missing-outcome.json +5 -0
- package/agent-template/.ai/blueprints/author-spec/examples/valid/input.json +6 -0
- package/agent-template/.ai/blueprints/author-spec/gates.yaml +13 -0
- package/agent-template/.ai/blueprints/author-spec/input.schema.json +20 -0
- package/agent-template/.ai/blueprints/author-spec/plan.schema.json +14 -0
- package/agent-template/.ai/blueprints/author-spec/required-files.yaml +6 -0
- package/agent-template/.ai/blueprints/author-spec/spec-requirements.yaml +35 -0
- package/agent-template/.ai/blueprints/author-spec/steps.yaml +28 -0
- package/agent-template/.ai/blueprints/author-spec/templates/module.yaml +45 -0
- package/agent-template/.ai/blueprints/bug-fix/README.md +5 -0
- package/agent-template/.ai/blueprints/bug-fix/allowed-paths.yaml +29 -0
- package/agent-template/.ai/blueprints/bug-fix/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/bug-fix/examples/invalid/input-no-symptom.json +4 -0
- package/agent-template/.ai/blueprints/bug-fix/examples/invalid/plan-no-test.json +8 -0
- package/agent-template/.ai/blueprints/bug-fix/examples/valid/input.json +6 -0
- package/agent-template/.ai/blueprints/bug-fix/examples/valid/plan.json +8 -0
- package/agent-template/.ai/blueprints/bug-fix/gates.yaml +30 -0
- package/agent-template/.ai/blueprints/bug-fix/input.schema.json +25 -0
- package/agent-template/.ai/blueprints/bug-fix/plan.schema.json +53 -0
- package/agent-template/.ai/blueprints/bug-fix/required-files.yaml +7 -0
- package/agent-template/.ai/blueprints/bug-fix/spec-requirements.yaml +7 -0
- package/agent-template/.ai/blueprints/bug-fix/steps.yaml +51 -0
- package/agent-template/.ai/blueprints/core-extend/README.md +5 -0
- package/agent-template/.ai/blueprints/core-extend/allowed-paths.yaml +49 -0
- package/agent-template/.ai/blueprints/core-extend/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/core-extend/examples/invalid/input-unknown-package.json +5 -0
- package/agent-template/.ai/blueprints/core-extend/examples/invalid/plan-missing-gates.json +7 -0
- package/agent-template/.ai/blueprints/core-extend/examples/valid/input.json +6 -0
- package/agent-template/.ai/blueprints/core-extend/examples/valid/plan.json +10 -0
- package/agent-template/.ai/blueprints/core-extend/gates.yaml +16 -0
- package/agent-template/.ai/blueprints/core-extend/input.schema.json +54 -0
- package/agent-template/.ai/blueprints/core-extend/plan.schema.json +39 -0
- package/agent-template/.ai/blueprints/core-extend/required-files.yaml +36 -0
- package/agent-template/.ai/blueprints/core-extend/spec-requirements.yaml +17 -0
- package/agent-template/.ai/blueprints/core-extend/steps.yaml +54 -0
- package/agent-template/.ai/blueprints/edit-module/README.md +9 -0
- package/agent-template/.ai/blueprints/edit-module/allowed-paths.yaml +27 -0
- package/agent-template/.ai/blueprints/edit-module/blueprint.json +20 -0
- package/agent-template/.ai/blueprints/edit-module/examples/invalid/input-unknown-change.json +5 -0
- package/agent-template/.ai/blueprints/edit-module/examples/invalid/plan-touches-platform.json +15 -0
- package/agent-template/.ai/blueprints/edit-module/examples/valid/input.json +5 -0
- package/agent-template/.ai/blueprints/edit-module/examples/valid/plan.json +25 -0
- package/agent-template/.ai/blueprints/edit-module/gates.yaml +30 -0
- package/agent-template/.ai/blueprints/edit-module/input.schema.json +31 -0
- package/agent-template/.ai/blueprints/edit-module/plan.schema.json +65 -0
- package/agent-template/.ai/blueprints/edit-module/required-files.yaml +80 -0
- package/agent-template/.ai/blueprints/edit-module/spec-requirements.yaml +15 -0
- package/agent-template/.ai/blueprints/edit-module/steps.yaml +115 -0
- package/agent-template/.ai/blueprints/new-module/README.md +7 -0
- package/agent-template/.ai/blueprints/new-module/allowed-paths.yaml +27 -0
- package/agent-template/.ai/blueprints/new-module/blueprint.json +20 -0
- package/agent-template/.ai/blueprints/new-module/examples/invalid/input-spec-outside-modules.json +4 -0
- package/agent-template/.ai/blueprints/new-module/examples/invalid/plan-unknown-gate.json +8 -0
- package/agent-template/.ai/blueprints/new-module/examples/valid/input.json +5 -0
- package/agent-template/.ai/blueprints/new-module/examples/valid/plan.json +22 -0
- package/agent-template/.ai/blueprints/new-module/gates.yaml +30 -0
- package/agent-template/.ai/blueprints/new-module/input.schema.json +21 -0
- package/agent-template/.ai/blueprints/new-module/plan.schema.json +58 -0
- package/agent-template/.ai/blueprints/new-module/required-files.yaml +73 -0
- package/agent-template/.ai/blueprints/new-module/spec-requirements.yaml +30 -0
- package/agent-template/.ai/blueprints/new-module/steps.yaml +138 -0
- package/agent-template/.ai/blueprints/release/README.md +5 -0
- package/agent-template/.ai/blueprints/release/allowed-paths.yaml +19 -0
- package/agent-template/.ai/blueprints/release/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/release/examples/invalid/input-bad-version.json +4 -0
- package/agent-template/.ai/blueprints/release/examples/invalid/plan-bad-branch.json +7 -0
- package/agent-template/.ai/blueprints/release/examples/valid/input.json +5 -0
- package/agent-template/.ai/blueprints/release/examples/valid/plan.json +20 -0
- package/agent-template/.ai/blueprints/release/gates.yaml +20 -0
- package/agent-template/.ai/blueprints/release/input.schema.json +24 -0
- package/agent-template/.ai/blueprints/release/plan.schema.json +46 -0
- package/agent-template/.ai/blueprints/release/required-files.yaml +19 -0
- package/agent-template/.ai/blueprints/release/spec-requirements.yaml +8 -0
- package/agent-template/.ai/blueprints/release/steps.yaml +47 -0
- package/agent-template/.ai/blueprints/security-review/README.md +5 -0
- package/agent-template/.ai/blueprints/security-review/allowed-paths.yaml +6 -0
- package/agent-template/.ai/blueprints/security-review/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/security-review/examples/invalid/input-unknown-kind.json +4 -0
- package/agent-template/.ai/blueprints/security-review/examples/invalid/plan-finding-without-scenario.json +14 -0
- package/agent-template/.ai/blueprints/security-review/examples/valid/input.json +4 -0
- package/agent-template/.ai/blueprints/security-review/examples/valid/plan.json +19 -0
- package/agent-template/.ai/blueprints/security-review/gates.yaml +22 -0
- package/agent-template/.ai/blueprints/security-review/input.schema.json +20 -0
- package/agent-template/.ai/blueprints/security-review/plan.schema.json +65 -0
- package/agent-template/.ai/blueprints/security-review/required-files.yaml +6 -0
- package/agent-template/.ai/blueprints/security-review/spec-requirements.yaml +9 -0
- package/agent-template/.ai/blueprints/security-review/steps.yaml +38 -0
- package/agent-template/.ai/examples/README.md +8 -0
- package/agent-template/.ai/examples/bad/client-imports-server/README.md +20 -0
- package/agent-template/.ai/examples/bad/client-imports-server/api.ts +12 -0
- package/agent-template/.ai/examples/bad/missing-acl/README.md +23 -0
- package/agent-template/.ai/examples/bad/missing-acl/endpoints.ts +12 -0
- package/agent-template/.ai/examples/bad/tenant-from-body/README.md +19 -0
- package/agent-template/.ai/examples/bad/tenant-from-body/endpoints.ts +33 -0
- package/agent-template/.ai/examples/client-contribution/CustomerListView.tsrx +34 -0
- package/agent-template/.ai/examples/client-contribution/README.md +11 -0
- package/agent-template/.ai/examples/client-contribution/contribution.tsrx +48 -0
- package/agent-template/.ai/examples/client-contribution/index.ts +20 -0
- package/agent-template/.ai/examples/client-contribution/permissions.ts +8 -0
- package/agent-template/.ai/examples/customer-cli-extension/README.md +14 -0
- package/agent-template/.ai/examples/customer-cli-extension/commands.json +17 -0
- package/agent-template/.ai/examples/customer-cli-extension/index.ts +36 -0
- package/agent-template/.ai/examples/module-create/task-packet.json +11 -0
- package/agent-template/.ai/guides/application-development.md +97 -0
- package/agent-template/.ai/policies/capabilities.yaml +164 -0
- package/agent-template/.ai/policies/model-routing.yaml +72 -0
- package/agent-template/.ai/policies/path-ownership.yaml +65 -0
- package/agent-template/.ai/policies/task-budgets.yaml +37 -0
- package/agent-template/.ai/references/catalog/LICENSE +21 -0
- package/agent-template/.ai/references/catalog/migrations/0001_catalog_core.down.sql +2 -0
- package/agent-template/.ai/references/catalog/migrations/0001_catalog_core.up.sql +21 -0
- package/agent-template/.ai/references/catalog/migrations/0002_catalog_history.down.sql +3 -0
- package/agent-template/.ai/references/catalog/migrations/0002_catalog_history.up.sql +20 -0
- package/agent-template/.ai/references/catalog/migrations/0003_catalog_history_service_actors.down.sql +3 -0
- package/agent-template/.ai/references/catalog/migrations/0003_catalog_history_service_actors.up.sql +36 -0
- package/agent-template/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.down.sql +3 -0
- package/agent-template/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.up.sql +19 -0
- package/agent-template/.ai/references/catalog/migrations/README.md +3 -0
- package/agent-template/.ai/references/catalog/module.json +27 -0
- package/agent-template/.ai/references/catalog/package.json +49 -0
- package/agent-template/.ai/references/catalog/spec/module.yaml +86 -0
- package/agent-template/.ai/references/catalog/src/acl/permissions.ts +6 -0
- package/agent-template/.ai/references/catalog/src/agent/tools.ts +164 -0
- package/agent-template/.ai/references/catalog/src/api/endpoints.ts +243 -0
- package/agent-template/.ai/references/catalog/src/client/CatalogHistoryDrawer.tsrx +123 -0
- package/agent-template/.ai/references/catalog/src/client/CatalogItemForm.tsrx +190 -0
- package/agent-template/.ai/references/catalog/src/client/CatalogView.tsrx +473 -0
- package/agent-template/.ai/references/catalog/src/client/api.ts +111 -0
- package/agent-template/.ai/references/catalog/src/client/contribution.tsrx +61 -0
- package/agent-template/.ai/references/catalog/src/client/index.ts +18 -0
- package/agent-template/.ai/references/catalog/src/client/navigation-copy.ts +9 -0
- package/agent-template/.ai/references/catalog/src/client/state.ts +24 -0
- package/agent-template/.ai/references/catalog/src/domain/types.ts +32 -0
- package/agent-template/.ai/references/catalog/src/domain/variables.ts +111 -0
- package/agent-template/.ai/references/catalog/src/index.ts +31 -0
- package/agent-template/.ai/references/catalog/src/platform.ts +35 -0
- package/agent-template/.ai/references/catalog/src/server/index.ts +4 -0
- package/agent-template/.ai/references/catalog/src/server/runtime.ts +86 -0
- package/agent-template/.ai/references/catalog/src/services/catalog-service.ts +306 -0
- package/agent-template/.ai/references/catalog/src/services/database-repository.ts +440 -0
- package/agent-template/.ai/references/catalog/src/services/index.ts +4 -0
- package/agent-template/.ai/references/catalog/src/services/migration.ts +171 -0
- package/agent-template/.ai/references/catalog/src/services/repository.ts +36 -0
- package/agent-template/.ai/references/catalog/src/services/target-idempotency.ts +59 -0
- package/agent-template/.ai/references/catalog/tests/agent-tools.test.ts +277 -0
- package/agent-template/.ai/references/catalog/tests/endpoints.test.ts +320 -0
- package/agent-template/.ai/references/catalog/tests/idempotency.test.ts +297 -0
- package/agent-template/.ai/references/catalog/tests/migrations.test.ts +149 -0
- package/agent-template/.ai/references/catalog/tests/module.test.ts +271 -0
- package/agent-template/.ai/references/catalog/tests/support/database.ts +76 -0
- package/agent-template/.ai/references/catalog/translations/en.json +101 -0
- package/agent-template/.ai/references/catalog/translations/pl.json +101 -0
- package/agent-template/.ai/references/catalog/tsconfig.json +15 -0
- package/agent-template/.ai/references/catalog/vitest.config.ts +16 -0
- package/agent-template/.ai/references/catalog.provenance.json +55 -0
- package/agent-template/.ai/rules/flowdular.md +86 -0
- package/agent-template/.ai/skills/README.md +36 -0
- package/agent-template/.ai/skills/agent-tool-design/SKILL.md +209 -0
- package/agent-template/.ai/skills/auth-security-review/SKILL.md +96 -0
- package/agent-template/.ai/skills/auto-review/SKILL.md +112 -0
- package/agent-template/.ai/skills/bug-hunt/SKILL.md +110 -0
- package/agent-template/.ai/skills/business-agent-design/SKILL.md +188 -0
- package/agent-template/.ai/skills/cli-extension/SKILL.md +114 -0
- package/agent-template/.ai/skills/core-extend/SKILL.md +104 -0
- package/agent-template/.ai/skills/database-adapter/SKILL.md +204 -0
- package/agent-template/.ai/skills/database-adapter/references/first-run-and-matrix.md +105 -0
- package/agent-template/.ai/skills/migration-authoring/SKILL.md +167 -0
- package/agent-template/.ai/skills/module-new/SKILL.md +180 -0
- package/agent-template/.ai/skills/module-update/SKILL.md +100 -0
- package/agent-template/.ai/skills/perf-audit/SKILL.md +105 -0
- package/agent-template/.ai/skills/release-eject-pr/SKILL.md +113 -0
- package/agent-template/.ai/skills/spec-approval/SKILL.md +112 -0
- package/agent-template/.ai/skills/test-hardening/SKILL.md +86 -0
- package/agent-template/.ai/skills/translations-i18n/SKILL.md +85 -0
- package/agent-template/.ai/skills/ux-design/SKILL.md +97 -0
- package/agent-template/.ai/skills/variables/SKILL.md +164 -0
- package/agent-template/.ai/skills/workflow-development/SKILL.md +199 -0
- package/agent-template/.claude/skills/agent-tool-design/SKILL.md +203 -0
- package/agent-template/.claude/skills/auth-security-review/SKILL.md +90 -0
- package/agent-template/.claude/skills/auto-review/SKILL.md +103 -0
- package/agent-template/.claude/skills/bug-hunt/SKILL.md +104 -0
- package/agent-template/.claude/skills/business-agent-design/SKILL.md +182 -0
- package/agent-template/.claude/skills/cli-extension/SKILL.md +108 -0
- package/agent-template/.claude/skills/core-extend/SKILL.md +99 -0
- package/agent-template/.claude/skills/database-adapter/SKILL.md +198 -0
- package/agent-template/.claude/skills/database-adapter/references/first-run-and-matrix.md +105 -0
- package/agent-template/.claude/skills/migration-authoring/SKILL.md +161 -0
- package/agent-template/.claude/skills/module-new/SKILL.md +171 -0
- package/agent-template/.claude/skills/module-update/SKILL.md +91 -0
- package/agent-template/.claude/skills/perf-audit/SKILL.md +98 -0
- package/agent-template/.claude/skills/release-eject-pr/SKILL.md +107 -0
- package/agent-template/.claude/skills/spec-approval/SKILL.md +106 -0
- package/agent-template/.claude/skills/test-hardening/SKILL.md +79 -0
- package/agent-template/.claude/skills/translations-i18n/SKILL.md +78 -0
- package/agent-template/.claude/skills/ux-design/SKILL.md +92 -0
- package/agent-template/.claude/skills/variables/SKILL.md +156 -0
- package/agent-template/.claude/skills/workflow-development/SKILL.md +192 -0
- package/agent-template/AGENTS.md +77 -0
- package/agent-template/CLAUDE.md +77 -0
- package/agent-template/docs/adr/0001-development-reload.md +16 -0
- package/agent-template/docs/adr/0002-durable-agent-execution.md +21 -0
- package/agent-template/docs/adr/0003-module-settings.md +22 -0
- package/agent-template/docs/adr/0004-enterprise-access-and-audit.md +36 -0
- package/agent-template/docs/adr/0005-sandbox-runtime-and-coding-agents.md +81 -0
- package/agent-template/docs/adr/0006-agentic-workflows.md +1702 -0
- package/agent-template/docs/adr/0007-module-owned-agents.md +429 -0
- package/agent-template/docs/adr/0008-database-adapter-contract.md +90 -0
- package/agent-template/docs/agent-contract.md +45 -0
- package/agent-template/docs/configuration.md +122 -0
- package/agent-template/docs/database-adapters.md +346 -0
- package/agent-template/docs/design-system.md +217 -0
- package/agent-template/docs/modules.md +146 -0
- package/agent-template/platform/scripts/build.mjs +38 -0
- package/agent-template/rulesync.jsonc +11 -0
- package/dist/bin.js +40 -2
- package/package.json +3 -2
- package/template/default/.prettierignore +9 -0
- package/template/default/README.md +12 -0
- package/template/default/flowdular.json +3 -3
- package/template/default/modules/example/package.json +2 -2
- package/template/default/package.json +6 -2
- package/template/default/platform/octane.config.ts +17 -6
- package/template/default/platform/package.json +3 -2
- package/template/default/pnpm-workspace.yaml +1 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type CatalogItemKind = 'product' | 'service';
|
|
2
|
+
|
|
3
|
+
export interface CatalogItem {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly tenantId: string;
|
|
6
|
+
readonly sku: string;
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly kind: CatalogItemKind;
|
|
9
|
+
readonly unit: string;
|
|
10
|
+
readonly basePriceMinor: number;
|
|
11
|
+
readonly currency: string;
|
|
12
|
+
readonly status: 'active' | 'archived';
|
|
13
|
+
readonly createdAt: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface CreateCatalogItemInput {
|
|
17
|
+
readonly sku: string;
|
|
18
|
+
readonly name: string;
|
|
19
|
+
readonly kind: CatalogItemKind;
|
|
20
|
+
readonly unit: string;
|
|
21
|
+
readonly basePriceMinor: number;
|
|
22
|
+
readonly currency: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface UpdateCatalogItemInput {
|
|
26
|
+
readonly id: string;
|
|
27
|
+
readonly name: string;
|
|
28
|
+
readonly kind: CatalogItemKind;
|
|
29
|
+
readonly unit: string;
|
|
30
|
+
readonly basePriceMinor: number;
|
|
31
|
+
readonly currency: string;
|
|
32
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { VariableSource } from '@flowdular/sdk/contracts';
|
|
2
|
+
import type {
|
|
3
|
+
AgentTool,
|
|
4
|
+
AgentToolContext,
|
|
5
|
+
} from '@flowdular/sdk/harness/runtime';
|
|
6
|
+
import {
|
|
7
|
+
VariableResolutionError,
|
|
8
|
+
type PlatformVariableRegistry,
|
|
9
|
+
type VariableSourceResolutionContext,
|
|
10
|
+
} from '@flowdular/sdk/kernel';
|
|
11
|
+
import { CATALOG_PERMISSIONS } from '../acl/permissions.ts';
|
|
12
|
+
import type { CatalogItem } from './types.ts';
|
|
13
|
+
|
|
14
|
+
export const CATALOG_VARIABLE_SOURCE: VariableSource = {
|
|
15
|
+
id: 'catalog.items',
|
|
16
|
+
variables: [
|
|
17
|
+
{
|
|
18
|
+
key: 'catalogItem.name',
|
|
19
|
+
label: 'Catalog item name',
|
|
20
|
+
kind: 'text',
|
|
21
|
+
scope: CATALOG_PERMISSIONS.read,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
key: 'catalogItem.sku',
|
|
25
|
+
label: 'Catalog item SKU',
|
|
26
|
+
kind: 'identifier',
|
|
27
|
+
scope: CATALOG_PERMISSIONS.read,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
key: 'catalogItem.priceMinor',
|
|
31
|
+
label: 'Catalog item price in minor units',
|
|
32
|
+
kind: 'money',
|
|
33
|
+
scope: CATALOG_PERMISSIONS.read,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
key: 'catalogItem.currency',
|
|
37
|
+
label: 'Catalog item currency',
|
|
38
|
+
kind: 'identifier',
|
|
39
|
+
scope: CATALOG_PERMISSIONS.read,
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function toolContext(
|
|
45
|
+
context: VariableSourceResolutionContext,
|
|
46
|
+
): AgentToolContext {
|
|
47
|
+
return {
|
|
48
|
+
runId:
|
|
49
|
+
context.actor.kind === 'agent'
|
|
50
|
+
? context.actor.runId!
|
|
51
|
+
: `variable:${context.actor.kind}:${context.actor.id}`,
|
|
52
|
+
tenantId: context.tenantId,
|
|
53
|
+
requestedBy: context.actor.id,
|
|
54
|
+
permissions: new Set(context.permissionSnapshot),
|
|
55
|
+
signal: context.signal,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* catalog.item.list is the module-owned public read tool. Its exact-id query
|
|
60
|
+
is bounded before paging, so the resolver never scans or opens catalog.db. */
|
|
61
|
+
export function registerCatalogVariableSource(
|
|
62
|
+
registry: PlatformVariableRegistry,
|
|
63
|
+
tools: readonly AgentTool[],
|
|
64
|
+
): void {
|
|
65
|
+
const list = tools.find((tool) => tool.id === 'catalog.item.list');
|
|
66
|
+
if (!list || !list.requiredPermissions.includes(CATALOG_PERMISSIONS.read)) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
'catalog.item.list must require catalog.items.read before variables can be registered.',
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
const requiredBindings = Object.fromEntries(
|
|
72
|
+
CATALOG_VARIABLE_SOURCE.variables.map((variable) => [
|
|
73
|
+
variable.key,
|
|
74
|
+
['catalogItemId'] as const,
|
|
75
|
+
]),
|
|
76
|
+
);
|
|
77
|
+
registry.register(CATALOG_VARIABLE_SOURCE, {
|
|
78
|
+
requiredBindings,
|
|
79
|
+
resolve: async (context) => {
|
|
80
|
+
if (
|
|
81
|
+
!list.requiredPermissions.every((permission) =>
|
|
82
|
+
context.permissionSnapshot.includes(permission),
|
|
83
|
+
)
|
|
84
|
+
) {
|
|
85
|
+
throw new VariableResolutionError(
|
|
86
|
+
'FORBIDDEN_TEMPLATE_VARIABLE',
|
|
87
|
+
'The variable source is unavailable to this actor.',
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
const rows = (await list.execute(
|
|
91
|
+
{ query: context.bindings.catalogItemId },
|
|
92
|
+
toolContext(context),
|
|
93
|
+
)) as readonly CatalogItem[];
|
|
94
|
+
const item = rows.find(
|
|
95
|
+
(candidate) => candidate.id === context.bindings.catalogItemId,
|
|
96
|
+
);
|
|
97
|
+
if (!item) {
|
|
98
|
+
throw new VariableResolutionError(
|
|
99
|
+
'VARIABLE_VALUE_UNAVAILABLE',
|
|
100
|
+
'A variable value is unavailable.',
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
'catalogItem.name': item.name,
|
|
105
|
+
'catalogItem.sku': item.sku,
|
|
106
|
+
'catalogItem.priceMinor': String(item.basePriceMinor),
|
|
107
|
+
'catalogItem.currency': item.currency,
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ModuleManifest,
|
|
3
|
+
RegisteredModule,
|
|
4
|
+
} from '@flowdular/sdk/contracts';
|
|
5
|
+
import manifest from '../module.json' with { type: 'json' };
|
|
6
|
+
import { CATALOG_PERMISSIONS } from './acl/permissions.ts';
|
|
7
|
+
|
|
8
|
+
export const moduleDefinition = {
|
|
9
|
+
manifest: manifest as ModuleManifest,
|
|
10
|
+
navigation: [
|
|
11
|
+
{
|
|
12
|
+
id: 'catalog.navigation',
|
|
13
|
+
label: 'Catalog',
|
|
14
|
+
href: '/catalog',
|
|
15
|
+
order: 20,
|
|
16
|
+
permission: CATALOG_PERMISSIONS.read,
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
permissions: Object.values(CATALOG_PERMISSIONS),
|
|
20
|
+
} satisfies RegisteredModule;
|
|
21
|
+
|
|
22
|
+
export { CATALOG_PERMISSIONS } from './acl/permissions.ts';
|
|
23
|
+
export {
|
|
24
|
+
CatalogService,
|
|
25
|
+
CatalogServiceError,
|
|
26
|
+
} from './services/catalog-service.ts';
|
|
27
|
+
export type {
|
|
28
|
+
CatalogItem,
|
|
29
|
+
CatalogItemKind,
|
|
30
|
+
CreateCatalogItemInput,
|
|
31
|
+
} from './domain/types.ts';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PlatformServerComposition,
|
|
3
|
+
PlatformServerContext,
|
|
4
|
+
} from '@flowdular/sdk/modules/auth/server';
|
|
5
|
+
import { platformVariableRegistry } from '@flowdular/sdk/kernel';
|
|
6
|
+
import { registerCatalogVariableSource } from './domain/variables.ts';
|
|
7
|
+
import {
|
|
8
|
+
catalogAgentTools,
|
|
9
|
+
createCatalogRoutes,
|
|
10
|
+
createCatalogRuntime,
|
|
11
|
+
} from './server/index.ts';
|
|
12
|
+
|
|
13
|
+
export function createServerComposition(
|
|
14
|
+
context: PlatformServerContext,
|
|
15
|
+
): PlatformServerComposition {
|
|
16
|
+
const runtime = createCatalogRuntime({
|
|
17
|
+
databases: context.databases,
|
|
18
|
+
purpose:
|
|
19
|
+
context.environment.NODE_ENV === 'test'
|
|
20
|
+
? 'test'
|
|
21
|
+
: context.environment.NODE_ENV === 'production'
|
|
22
|
+
? 'runtime'
|
|
23
|
+
: 'preview',
|
|
24
|
+
});
|
|
25
|
+
const tools = catalogAgentTools(runtime);
|
|
26
|
+
context.agentTools.register(tools);
|
|
27
|
+
registerCatalogVariableSource(
|
|
28
|
+
platformVariableRegistry(context.capabilities),
|
|
29
|
+
tools,
|
|
30
|
+
);
|
|
31
|
+
return {
|
|
32
|
+
routes: createCatalogRoutes(context.auth, runtime),
|
|
33
|
+
dispose: () => runtime.dispose(),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DatabaseAdapterLease,
|
|
3
|
+
DatabaseProvider,
|
|
4
|
+
DatabaseProviderRequest,
|
|
5
|
+
} from '@flowdular/sdk/database';
|
|
6
|
+
import {
|
|
7
|
+
DATABASE_CAPABILITY_IDS,
|
|
8
|
+
DATABASE_DIALECT_IDS,
|
|
9
|
+
} from '@flowdular/sdk/database';
|
|
10
|
+
import { CatalogService } from '../services/catalog-service.ts';
|
|
11
|
+
import {
|
|
12
|
+
DatabaseCatalogRepository,
|
|
13
|
+
migrateCatalogDatabase,
|
|
14
|
+
} from '../services/database-repository.ts';
|
|
15
|
+
|
|
16
|
+
export interface CatalogRuntimeOptions {
|
|
17
|
+
readonly databases: DatabaseProvider;
|
|
18
|
+
readonly purpose: Exclude<DatabaseProviderRequest['purpose'], 'migration'>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface CatalogRuntime {
|
|
22
|
+
service(): Promise<CatalogService>;
|
|
23
|
+
dispose(): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function createCatalogRuntime(
|
|
27
|
+
options: CatalogRuntimeOptions,
|
|
28
|
+
): CatalogRuntime {
|
|
29
|
+
let disposed = false;
|
|
30
|
+
let runtimeLeasePromise: Promise<DatabaseAdapterLease> | undefined;
|
|
31
|
+
let servicePromise: Promise<CatalogService> | undefined;
|
|
32
|
+
|
|
33
|
+
const initialize = async (): Promise<CatalogService> => {
|
|
34
|
+
/* Migrations take their own short lease: the runtime role is tenant
|
|
35
|
+
scoped and may not run schema operations. */
|
|
36
|
+
const migrationLease = await options.databases.acquire({
|
|
37
|
+
namespace: 'catalog.core',
|
|
38
|
+
purpose: 'migration',
|
|
39
|
+
requirements: {
|
|
40
|
+
dialectIds: [DATABASE_DIALECT_IDS.postgresql],
|
|
41
|
+
capabilities: [
|
|
42
|
+
DATABASE_CAPABILITY_IDS.MIGRATION_LOCK,
|
|
43
|
+
DATABASE_CAPABILITY_IDS.SCHEMA_INTROSPECTION,
|
|
44
|
+
DATABASE_CAPABILITY_IDS.TRANSACTIONAL_DDL,
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
try {
|
|
49
|
+
await migrateCatalogDatabase(migrationLease.database);
|
|
50
|
+
} finally {
|
|
51
|
+
await migrationLease.release();
|
|
52
|
+
}
|
|
53
|
+
runtimeLeasePromise = options.databases.acquire({
|
|
54
|
+
namespace: 'catalog.core',
|
|
55
|
+
purpose: options.purpose,
|
|
56
|
+
requirements: {
|
|
57
|
+
dialectIds: [DATABASE_DIALECT_IDS.postgresql],
|
|
58
|
+
capabilities: [DATABASE_CAPABILITY_IDS.TRANSACTIONS],
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
const lease = await runtimeLeasePromise;
|
|
62
|
+
return new CatalogService(new DatabaseCatalogRepository(lease.database));
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
service: () => {
|
|
67
|
+
if (disposed) {
|
|
68
|
+
return Promise.reject(new Error('Catalog runtime is disposed.'));
|
|
69
|
+
}
|
|
70
|
+
servicePromise ??= initialize();
|
|
71
|
+
return servicePromise;
|
|
72
|
+
},
|
|
73
|
+
async dispose() {
|
|
74
|
+
if (disposed) return;
|
|
75
|
+
disposed = true;
|
|
76
|
+
if (!runtimeLeasePromise) {
|
|
77
|
+
await servicePromise?.catch(() => undefined);
|
|
78
|
+
}
|
|
79
|
+
if (!runtimeLeasePromise) return;
|
|
80
|
+
const lease = await runtimeLeasePromise;
|
|
81
|
+
await lease.release();
|
|
82
|
+
runtimeLeasePromise = undefined;
|
|
83
|
+
servicePromise = undefined;
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import {
|
|
3
|
+
normalizeActor,
|
|
4
|
+
type Actor,
|
|
5
|
+
type HistoryPage,
|
|
6
|
+
type HistoryRequest,
|
|
7
|
+
} from '@flowdular/sdk/kernel';
|
|
8
|
+
import type {
|
|
9
|
+
CatalogItem,
|
|
10
|
+
CatalogItemKind,
|
|
11
|
+
CreateCatalogItemInput,
|
|
12
|
+
UpdateCatalogItemInput,
|
|
13
|
+
} from '../domain/types.ts';
|
|
14
|
+
import { DuplicateSkuError, type CatalogRepository } from './repository.ts';
|
|
15
|
+
import {
|
|
16
|
+
canonicalDigest,
|
|
17
|
+
TargetIdempotencyConflictError,
|
|
18
|
+
} from './target-idempotency.ts';
|
|
19
|
+
|
|
20
|
+
export interface CatalogIdempotencyRequest {
|
|
21
|
+
readonly key: string;
|
|
22
|
+
readonly operationId: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class CatalogServiceError extends Error {
|
|
26
|
+
constructor(
|
|
27
|
+
readonly code: string,
|
|
28
|
+
message: string,
|
|
29
|
+
readonly status = 400,
|
|
30
|
+
) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = 'CatalogServiceError';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function bounded(
|
|
37
|
+
value: string,
|
|
38
|
+
field: string,
|
|
39
|
+
min: number,
|
|
40
|
+
max: number,
|
|
41
|
+
): string {
|
|
42
|
+
const normalized = value.trim();
|
|
43
|
+
if (normalized.length < min || normalized.length > max) {
|
|
44
|
+
throw new CatalogServiceError(
|
|
45
|
+
'INVALID_INPUT',
|
|
46
|
+
`${field} must contain between ${min} and ${max} characters.`,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return normalized;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function itemKind(value: CatalogItemKind): CatalogItemKind {
|
|
53
|
+
if (value !== 'product' && value !== 'service') {
|
|
54
|
+
throw new CatalogServiceError(
|
|
55
|
+
'INVALID_ITEM_KIND',
|
|
56
|
+
'kind must be product or service.',
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function trustedActor(actor: Actor): Actor {
|
|
63
|
+
const normalized = normalizeActor(actor);
|
|
64
|
+
if (!normalized) {
|
|
65
|
+
throw new CatalogServiceError(
|
|
66
|
+
'INVALID_ACTOR',
|
|
67
|
+
'actor must carry a kind, an id, and a label.',
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return normalized;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export class CatalogService {
|
|
74
|
+
constructor(private readonly repository: CatalogRepository) {}
|
|
75
|
+
|
|
76
|
+
async list(tenantId: string): Promise<readonly CatalogItem[]> {
|
|
77
|
+
return await this.repository.list(bounded(tenantId, 'tenantId', 1, 128));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async get(tenantId: string, id: string): Promise<CatalogItem | null> {
|
|
81
|
+
return await this.repository.find(
|
|
82
|
+
bounded(tenantId, 'tenantId', 1, 128),
|
|
83
|
+
bounded(id, 'id', 1, 128),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async create(
|
|
88
|
+
tenantId: string,
|
|
89
|
+
input: CreateCatalogItemInput,
|
|
90
|
+
actor: Actor,
|
|
91
|
+
): Promise<CatalogItem> {
|
|
92
|
+
const item = this.newItem(tenantId, input);
|
|
93
|
+
return await this.persistCreate(item, trustedActor(actor));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async createIdempotent(
|
|
97
|
+
tenantId: string,
|
|
98
|
+
input: CreateCatalogItemInput,
|
|
99
|
+
actor: Actor,
|
|
100
|
+
idempotency: CatalogIdempotencyRequest,
|
|
101
|
+
): Promise<CatalogItem> {
|
|
102
|
+
const item = this.newItem(tenantId, input);
|
|
103
|
+
const trusted = trustedActor(actor);
|
|
104
|
+
try {
|
|
105
|
+
return await this.repository.createIdempotent(
|
|
106
|
+
item,
|
|
107
|
+
item.sku.toLocaleLowerCase('en-US'),
|
|
108
|
+
trusted,
|
|
109
|
+
{
|
|
110
|
+
key: bounded(idempotency.key, 'idempotencyKey', 8, 128),
|
|
111
|
+
operationId: bounded(idempotency.operationId, 'operationId', 3, 160),
|
|
112
|
+
inputDigest: canonicalDigest({
|
|
113
|
+
sku: item.sku,
|
|
114
|
+
name: item.name,
|
|
115
|
+
kind: item.kind,
|
|
116
|
+
unit: item.unit,
|
|
117
|
+
basePriceMinor: item.basePriceMinor,
|
|
118
|
+
currency: item.currency,
|
|
119
|
+
}),
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
if (error instanceof TargetIdempotencyConflictError) {
|
|
124
|
+
throw new CatalogServiceError(error.code, error.message, 409);
|
|
125
|
+
}
|
|
126
|
+
if (error instanceof DuplicateSkuError) {
|
|
127
|
+
throw new CatalogServiceError('DUPLICATE_SKU', error.message, 409);
|
|
128
|
+
}
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private newItem(
|
|
134
|
+
tenantId: string,
|
|
135
|
+
input: CreateCatalogItemInput,
|
|
136
|
+
): CatalogItem {
|
|
137
|
+
if (
|
|
138
|
+
!Number.isSafeInteger(input.basePriceMinor) ||
|
|
139
|
+
input.basePriceMinor < 0
|
|
140
|
+
) {
|
|
141
|
+
throw new CatalogServiceError(
|
|
142
|
+
'INVALID_PRICE',
|
|
143
|
+
'basePriceMinor must be a non-negative integer.',
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
const sku = bounded(input.sku, 'sku', 1, 64).toUpperCase();
|
|
147
|
+
const currency = bounded(input.currency, 'currency', 3, 3).toUpperCase();
|
|
148
|
+
if (!/^[A-Z]{3}$/.test(currency)) {
|
|
149
|
+
throw new CatalogServiceError(
|
|
150
|
+
'INVALID_CURRENCY',
|
|
151
|
+
'currency must be a three-letter ISO code.',
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
id: randomUUID(),
|
|
156
|
+
tenantId: bounded(tenantId, 'tenantId', 1, 128),
|
|
157
|
+
sku,
|
|
158
|
+
name: bounded(input.name, 'name', 2, 160),
|
|
159
|
+
kind: itemKind(input.kind),
|
|
160
|
+
unit: bounded(input.unit, 'unit', 1, 24),
|
|
161
|
+
basePriceMinor: input.basePriceMinor,
|
|
162
|
+
currency,
|
|
163
|
+
status: 'active',
|
|
164
|
+
createdAt: Date.now(),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private async persistCreate(
|
|
169
|
+
item: CatalogItem,
|
|
170
|
+
actor: Actor,
|
|
171
|
+
): Promise<CatalogItem> {
|
|
172
|
+
try {
|
|
173
|
+
return await this.repository.create(
|
|
174
|
+
item,
|
|
175
|
+
item.sku.toLocaleLowerCase('en-US'),
|
|
176
|
+
actor,
|
|
177
|
+
);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if (error instanceof DuplicateSkuError) {
|
|
180
|
+
throw new CatalogServiceError('DUPLICATE_SKU', error.message, 409);
|
|
181
|
+
}
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async update(
|
|
187
|
+
tenantId: string,
|
|
188
|
+
input: UpdateCatalogItemInput,
|
|
189
|
+
actor: Actor,
|
|
190
|
+
): Promise<CatalogItem> {
|
|
191
|
+
const trustedTenantId = bounded(tenantId, 'tenantId', 1, 128);
|
|
192
|
+
const current = await this.item(trustedTenantId, input.id);
|
|
193
|
+
if (
|
|
194
|
+
!Number.isSafeInteger(input.basePriceMinor) ||
|
|
195
|
+
input.basePriceMinor < 0
|
|
196
|
+
) {
|
|
197
|
+
throw new CatalogServiceError(
|
|
198
|
+
'INVALID_PRICE',
|
|
199
|
+
'basePriceMinor must be a non-negative integer.',
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
const currency = bounded(input.currency, 'currency', 3, 3).toUpperCase();
|
|
203
|
+
if (!/^[A-Z]{3}$/.test(currency)) {
|
|
204
|
+
throw new CatalogServiceError(
|
|
205
|
+
'INVALID_CURRENCY',
|
|
206
|
+
'currency must be a three-letter ISO code.',
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
const updated = await this.repository.update(
|
|
210
|
+
{
|
|
211
|
+
...current,
|
|
212
|
+
name: bounded(input.name, 'name', 2, 160),
|
|
213
|
+
kind: itemKind(input.kind),
|
|
214
|
+
unit: bounded(input.unit, 'unit', 1, 24),
|
|
215
|
+
basePriceMinor: input.basePriceMinor,
|
|
216
|
+
currency,
|
|
217
|
+
},
|
|
218
|
+
trustedActor(actor),
|
|
219
|
+
);
|
|
220
|
+
if (!updated) throw this.notFound();
|
|
221
|
+
return updated;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async archive(
|
|
225
|
+
tenantId: string,
|
|
226
|
+
id: string,
|
|
227
|
+
actor: Actor,
|
|
228
|
+
): Promise<CatalogItem> {
|
|
229
|
+
return await this.changeStatus(tenantId, id, 'archived', actor);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async restore(
|
|
233
|
+
tenantId: string,
|
|
234
|
+
id: string,
|
|
235
|
+
actor: Actor,
|
|
236
|
+
): Promise<CatalogItem> {
|
|
237
|
+
return await this.changeStatus(tenantId, id, 'active', actor);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async delete(tenantId: string, id: string, actor: Actor): Promise<void> {
|
|
241
|
+
const trustedTenantId = bounded(tenantId, 'tenantId', 1, 128);
|
|
242
|
+
const trustedId = bounded(id, 'id', 1, 128);
|
|
243
|
+
const current = await this.item(trustedTenantId, trustedId);
|
|
244
|
+
if (current.status !== 'archived') {
|
|
245
|
+
throw new CatalogServiceError(
|
|
246
|
+
'CATALOG_ITEM_NOT_ARCHIVED',
|
|
247
|
+
'Archive the catalog item before deleting it permanently.',
|
|
248
|
+
409,
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
if (
|
|
252
|
+
!(await this.repository.delete(
|
|
253
|
+
trustedTenantId,
|
|
254
|
+
trustedId,
|
|
255
|
+
trustedActor(actor),
|
|
256
|
+
))
|
|
257
|
+
) {
|
|
258
|
+
throw this.notFound();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async history(
|
|
263
|
+
tenantId: string,
|
|
264
|
+
request: HistoryRequest,
|
|
265
|
+
): Promise<HistoryPage> {
|
|
266
|
+
return await this.repository.history({
|
|
267
|
+
tenantId: bounded(tenantId, 'tenantId', 1, 128),
|
|
268
|
+
recordId: bounded(request.recordId, 'recordId', 1, 128),
|
|
269
|
+
limit: request.limit,
|
|
270
|
+
cursor: request.cursor,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private async item(tenantId: string, id: string): Promise<CatalogItem> {
|
|
275
|
+
const item = await this.repository.find(
|
|
276
|
+
tenantId,
|
|
277
|
+
bounded(id, 'id', 1, 128),
|
|
278
|
+
);
|
|
279
|
+
if (!item) throw this.notFound();
|
|
280
|
+
return item;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private notFound(): CatalogServiceError {
|
|
284
|
+
return new CatalogServiceError(
|
|
285
|
+
'CATALOG_ITEM_NOT_FOUND',
|
|
286
|
+
'The catalog item was not found in the active tenant.',
|
|
287
|
+
404,
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private async changeStatus(
|
|
292
|
+
tenantId: string,
|
|
293
|
+
id: string,
|
|
294
|
+
status: CatalogItem['status'],
|
|
295
|
+
actor: Actor,
|
|
296
|
+
): Promise<CatalogItem> {
|
|
297
|
+
const item = await this.repository.setStatus(
|
|
298
|
+
bounded(tenantId, 'tenantId', 1, 128),
|
|
299
|
+
bounded(id, 'id', 1, 128),
|
|
300
|
+
status,
|
|
301
|
+
trustedActor(actor),
|
|
302
|
+
);
|
|
303
|
+
if (!item) throw this.notFound();
|
|
304
|
+
return item;
|
|
305
|
+
}
|
|
306
|
+
}
|