@open-mercato/cli 0.6.7 → 0.6.8-develop.6875.1.871a4afc94
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/.turbo/turbo-build.log +4 -4
- package/build.mjs +24 -7
- package/dist/agentic/claude-code/hooks/entity-migration-check.ts +32 -88
- package/dist/agentic/codex/enforcement-rules.md +2 -16
- package/dist/agentic/cursor/hooks/entity-migration-check.mjs +19 -50
- package/dist/agentic/cursor/rules/entity-guard.mdc +9 -21
- package/dist/agentic/cursor/rules/generated-guard.mdc +3 -5
- package/dist/agentic/cursor/rules/open-mercato.mdc +3 -29
- package/dist/agentic/guides/ai-workflows.md +76 -0
- package/dist/agentic/guides/architecture.md +107 -0
- package/dist/agentic/guides/backend-ui.md +80 -0
- package/dist/agentic/guides/contracts.md +92 -0
- package/dist/agentic/guides/extensions.md +121 -0
- package/dist/agentic/guides/framework-extension-points.md +36 -0
- package/dist/agentic/guides/integrations.md +81 -0
- package/dist/agentic/guides/module-facts.json +115537 -3951
- package/dist/agentic/guides/module-system.md +12 -127
- package/dist/agentic/guides/modules/ai_assistant.md +227 -28
- package/dist/agentic/guides/modules/api_docs.md +58 -2
- package/dist/agentic/guides/modules/api_keys.md +92 -7
- package/dist/agentic/guides/modules/attachments.md +119 -14
- package/dist/agentic/guides/modules/audit_logs.md +131 -10
- package/dist/agentic/guides/modules/auth.md +276 -33
- package/dist/agentic/guides/modules/business_rules.md +149 -14
- package/dist/agentic/guides/modules/catalog.md +475 -43
- package/dist/agentic/guides/modules/channel_gmail.md +82 -3
- package/dist/agentic/guides/modules/channel_imap.md +82 -3
- package/dist/agentic/guides/modules/checkout.md +299 -27
- package/dist/agentic/guides/modules/communication_channels.md +315 -33
- package/dist/agentic/guides/modules/configs.md +104 -8
- package/dist/agentic/guides/modules/content.md +57 -2
- package/dist/agentic/guides/modules/currencies.md +152 -17
- package/dist/agentic/guides/modules/customer_accounts.md +387 -45
- package/dist/agentic/guides/modules/customers.md +746 -85
- package/dist/agentic/guides/modules/dashboards.md +155 -10
- package/dist/agentic/guides/modules/data_sync.md +149 -16
- package/dist/agentic/guides/modules/design_system.md +102 -0
- package/dist/agentic/guides/modules/dictionaries.md +107 -12
- package/dist/agentic/guides/modules/directory.md +148 -15
- package/dist/agentic/guides/modules/entities.md +145 -13
- package/dist/agentic/guides/modules/events.md +59 -2
- package/dist/agentic/guides/modules/feature_toggles.md +119 -9
- package/dist/agentic/guides/modules/gateway_stripe.md +92 -4
- package/dist/agentic/guides/modules/generators.md +45 -2
- package/dist/agentic/guides/modules/inbox_ops.md +207 -27
- package/dist/agentic/guides/modules/integrations.md +146 -16
- package/dist/agentic/guides/modules/messages.md +223 -24
- package/dist/agentic/guides/modules/notifications.md +96 -8
- package/dist/agentic/guides/modules/onboarding.md +77 -6
- package/dist/agentic/guides/modules/payment_gateways.md +164 -21
- package/dist/agentic/guides/modules/perspectives.md +77 -7
- package/dist/agentic/guides/modules/planner.md +136 -18
- package/dist/agentic/guides/modules/portal.md +93 -2
- package/dist/agentic/guides/modules/progress.md +104 -14
- package/dist/agentic/guides/modules/query_index.md +154 -15
- package/dist/agentic/guides/modules/record_locks.md +206 -21
- package/dist/agentic/guides/modules/resources.md +197 -30
- package/dist/agentic/guides/modules/sales.md +678 -84
- package/dist/agentic/guides/modules/scheduler.md +133 -14
- package/dist/agentic/guides/modules/search.md +170 -6
- package/dist/agentic/guides/modules/security.md +294 -35
- package/dist/agentic/guides/modules/shipping_carriers.md +134 -18
- package/dist/agentic/guides/modules/sso.md +151 -26
- package/dist/agentic/guides/modules/staff.md +369 -57
- package/dist/agentic/guides/modules/storage_s3.md +95 -5
- package/dist/agentic/guides/modules/sync_akeneo.md +85 -3
- package/dist/agentic/guides/modules/sync_excel.md +75 -6
- package/dist/agentic/guides/modules/system_status_overlays.md +60 -2
- package/dist/agentic/guides/modules/translations.md +104 -10
- package/dist/agentic/guides/modules/webhooks.md +203 -24
- package/dist/agentic/guides/modules/wms.md +441 -46
- package/dist/agentic/guides/modules/workflows.md +302 -43
- package/dist/agentic/guides/spec-delivery.md +22 -0
- package/dist/agentic/guides/testing-debugging.md +80 -0
- package/dist/agentic/guides/upstream/AGENTS.md +299 -0
- package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +316 -0
- package/dist/agentic/guides/upstream/manifest.json +8 -0
- package/dist/agentic/shared/AGENTS.md.template +124 -311
- package/dist/agentic/shared/ai/agentic.config.json +2 -0
- package/dist/agentic/shared/ai/harness/README.md +50 -0
- package/dist/agentic/shared/ai/harness/RELEASE.md +57 -0
- package/dist/agentic/shared/ai/harness/cases.json +13680 -0
- package/dist/agentic/shared/ai/harness/cases.schema.json +133 -0
- package/dist/agentic/shared/ai/harness/fixtures/index.json +49 -0
- package/dist/agentic/shared/ai/harness/fixtures/seeds.json +162 -0
- package/dist/agentic/shared/ai/harness/generated-code-review-policy.md +21 -0
- package/dist/agentic/shared/ai/harness/generated-code-review-response.schema.json +96 -0
- package/dist/agentic/shared/ai/harness/generated-code-review-result.schema.json +172 -0
- package/dist/agentic/shared/ai/harness/release-matrix.json +158 -0
- package/dist/agentic/shared/ai/harness/release-result.schema.json +219 -0
- package/dist/agentic/shared/ai/harness/result.schema.json +76 -0
- package/dist/agentic/shared/ai/harness/routing-response.schema.json +12 -0
- package/dist/agentic/shared/ai/harness/target-validation-result.schema.json +73 -0
- package/dist/agentic/shared/ai/harness/validators.json +78 -0
- package/dist/agentic/shared/ai/harness/writable-ast-oracles.mjs +2121 -0
- package/dist/agentic/shared/ai/harness/writable-behavior-oracles.mjs +1096 -0
- package/dist/agentic/shared/ai/lessons/_template.md +16 -0
- package/dist/agentic/shared/ai/lessons.md +26 -3
- package/dist/agentic/shared/ai/review-checklist.md +36 -0
- package/dist/agentic/shared/ai/skills/om-auto-implement-spec/SKILL.md +21 -0
- package/dist/agentic/shared/ai/skills/om-auto-review-pr/SKILL.md +4 -0
- package/dist/agentic/shared/ai/skills/om-auto-upgrade-0.6.6-to-0.6.7/SKILL.md +98 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/SKILL.md +20 -241
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/crud-surfaces.md +16 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/frontend-and-design-system.md +53 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/page-and-navigation.md +13 -0
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/quality-states.md +11 -0
- package/dist/agentic/shared/ai/skills/om-build-workflow/SKILL.md +30 -0
- package/dist/agentic/shared/ai/skills/om-build-workflow/references/activity-contracts.md +12 -0
- package/dist/agentic/shared/ai/skills/om-build-workflow/references/durability-and-progress.md +13 -0
- package/dist/agentic/shared/ai/skills/om-build-workflow/references/workflow-design.md +14 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/SKILL.md +28 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/attachments-and-overrides.md +12 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/module-agents-and-tools.md +22 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/orchestrator-agents.md +12 -0
- package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/surface-selector.md +19 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/SKILL.md +16 -601
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/integrity-and-concurrency.md +19 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/migration-workflow.md +13 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/schema-design.md +14 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/sensitive-data.md +10 -0
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/SKILL.md +14 -310
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/references/decision-and-procedure.md +17 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/SKILL.md +29 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-template.md +100 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-workflow.md +16 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/references/owner-selection.md +17 -0
- package/dist/agentic/shared/ai/skills/om-framework-context/SKILL.md +25 -0
- package/dist/agentic/shared/ai/skills/om-framework-context/references/resolver-procedure.md +11 -0
- package/dist/agentic/shared/ai/skills/om-framework-context/references/skew-and-escalation.md +13 -0
- package/dist/agentic/shared/ai/skills/om-help/SKILL.md +15 -97
- package/dist/agentic/shared/ai/skills/om-help/references/delivery-workflows.md +18 -0
- package/dist/agentic/shared/ai/skills/om-help/references/task-families.md +20 -0
- package/dist/agentic/shared/ai/skills/om-implement-spec/SKILL.md +33 -152
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/phases-and-gates.md +33 -0
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/planning-and-progress.md +40 -0
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/report-templates.md +27 -0
- package/dist/agentic/shared/ai/skills/om-implement-spec/references/spec-resolution.md +24 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/SKILL.md +24 -708
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/package-and-activation.md +16 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/provider-families.md +14 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/security-and-reliability.md +15 -0
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/sync-and-files.md +12 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/SKILL.md +34 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/agents/openai.yaml +4 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/agentic-setup.md +11 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/input-normalization.md +31 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/judge-workflow.md +39 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/report-template.md +43 -0
- package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/rules.md +11 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/SKILL.md +20 -820
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/api-and-domain.md +19 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/business-one-shot-blueprints.md +172 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/data-and-migrations.md +12 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/discovery-surface-catalog.md +83 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/module-surfaces.md +21 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/planning.md +11 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/runtime-cache-and-queues.md +22 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/verification.md +13 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/SKILL.md +41 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/agentic-setup.md +19 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/bundle-preparation.md +44 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/consent-and-review.md +39 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/publication.md +20 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/report-templates.md +57 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/references/rules.md +11 -0
- package/dist/agentic/shared/ai/skills/om-share-this-session/scripts/prepare-share-bundle.mjs +486 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/SKILL.md +19 -843
- package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-branches.md +18 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/mechanism-selector.md +27 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/read-write-roundtrip.md +13 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/unified-overrides.md +41 -0
- package/dist/agentic/shared/ai/skills/om-system-extension/references/verification.md +11 -0
- package/dist/agentic/shared/ai/skills/om-trim-unused-modules/SKILL.md +15 -63
- package/dist/agentic/shared/ai/skills/om-trim-unused-modules/references/dependency-and-redirect.md +15 -0
- package/dist/agentic/shared/ai/skills/om-troubleshooter/SKILL.md +16 -467
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnosis-map.md +16 -0
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/regression-oracles.md +14 -0
- package/dist/agentic/shared/ai/skills/tiers.json +143 -21
- package/dist/agentic/shared/ai/skills/tiers.schema.json +52 -40
- package/dist/agentic/shared/ai/specs/README.md +4 -3
- package/dist/agentic/shared/ai/specs/SPEC-000-template.md +226 -10
- package/dist/agentic/shared/ai/trackers/github.md +108 -4
- package/dist/agentic/shared/scripts/agent-harness-tool-server.mjs +187 -0
- package/dist/agentic/shared/scripts/check-lessons.mjs +178 -0
- package/dist/agentic/shared/scripts/evaluate-agent-harness.mjs +2958 -0
- package/dist/agentic/shared/scripts/execution-sandbox.mjs +329 -0
- package/dist/agentic/shared/scripts/framework-context.mjs +888 -0
- package/dist/agentic/shared/scripts/install-skills.mjs +1029 -0
- package/dist/agentic/shared/scripts/install-skills.sh +4 -694
- package/dist/agentic/shared/scripts/prepare-agent-harness-fixture.mjs +222 -0
- package/dist/agentic/shared/scripts/run-agent-harness-release.mjs +1774 -0
- package/dist/bin.js +13 -1
- package/dist/bin.js.map +2 -2
- package/dist/lib/__integration__/TC-INT-008.spec.js +88 -8
- package/dist/lib/__integration__/TC-INT-008.spec.js.map +2 -2
- package/dist/lib/agentic-init.js +13 -3
- package/dist/lib/agentic-init.js.map +2 -2
- package/dist/lib/agentic-setup.js +434 -186
- package/dist/lib/agentic-setup.js.map +2 -2
- package/dist/lib/generators/extensions/injection-widgets.js +20 -0
- package/dist/lib/generators/extensions/injection-widgets.js.map +2 -2
- package/dist/lib/generators/module-extension-facts.js +1840 -0
- package/dist/lib/generators/module-extension-facts.js.map +7 -0
- package/dist/lib/generators/module-fact-sources.js +70 -0
- package/dist/lib/generators/module-fact-sources.js.map +7 -0
- package/dist/lib/generators/module-facts-discovery.js +41 -7
- package/dist/lib/generators/module-facts-discovery.js.map +2 -2
- package/dist/lib/generators/module-facts.js +1387 -45
- package/dist/lib/generators/module-facts.js.map +3 -3
- package/dist/lib/generators/module-override-targets.js +688 -0
- package/dist/lib/generators/module-override-targets.js.map +7 -0
- package/dist/lib/generators/module-registry.js +11 -5
- package/dist/lib/generators/module-registry.js.map +2 -2
- package/dist/lib/load-env.js +24 -0
- package/dist/lib/load-env.js.map +7 -0
- package/dist/lib/resolver.js +2 -0
- package/dist/lib/resolver.js.map +2 -2
- package/dist/lib/telemetry-init.js +358 -0
- package/dist/lib/telemetry-init.js.map +7 -0
- package/dist/lib/testing/integration.js +5 -0
- package/dist/lib/testing/integration.js.map +2 -2
- package/dist/mercato.js +23 -1
- package/dist/mercato.js.map +2 -2
- package/package.json +13 -11
- package/src/bin.ts +33 -1
- package/src/lib/__integration__/TC-INT-008.spec.ts +107 -9
- package/src/lib/__tests__/agentic-init.test.ts +41 -5
- package/src/lib/__tests__/agentic-setup.ownership.test.ts +386 -0
- package/src/lib/__tests__/injection-widget-id-validation.test.ts +184 -0
- package/src/lib/__tests__/load-env.test.ts +65 -0
- package/src/lib/__tests__/telemetry-init.test.ts +298 -0
- package/src/lib/agentic-init.ts +15 -3
- package/src/lib/agentic-setup.ts +573 -222
- package/src/lib/generators/__tests__/activation-adapter-coverage.test.ts +92 -0
- package/src/lib/generators/__tests__/module-extension-activations.test.ts +762 -0
- package/src/lib/generators/__tests__/module-extension-facts.test.ts +404 -0
- package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +88 -2
- package/src/lib/generators/__tests__/module-facts.customers.fixture.test.ts +1 -0
- package/src/lib/generators/__tests__/module-facts.discovery.test.ts +54 -10
- package/src/lib/generators/__tests__/module-facts.extension-hosts.test.ts +108 -0
- package/src/lib/generators/__tests__/module-facts.owned-contracts.test.ts +561 -0
- package/src/lib/generators/__tests__/module-facts.source-links.test.ts +119 -0
- package/src/lib/generators/__tests__/module-override-targets.roundtrip.test.ts +149 -0
- package/src/lib/generators/__tests__/module-override-targets.test.ts +304 -0
- package/src/lib/generators/__tests__/module-subset.test.ts +18 -0
- package/src/lib/generators/extensions/injection-widgets.ts +51 -0
- package/src/lib/generators/module-extension-facts.ts +2365 -0
- package/src/lib/generators/module-fact-sources.ts +108 -0
- package/src/lib/generators/module-facts-discovery.ts +58 -7
- package/src/lib/generators/module-facts.ts +1851 -46
- package/src/lib/generators/module-override-targets.ts +863 -0
- package/src/lib/generators/module-registry.ts +16 -6
- package/src/lib/load-env.ts +30 -0
- package/src/lib/resolver.ts +3 -0
- package/src/lib/telemetry-init.ts +461 -0
- package/src/lib/testing/__tests__/integration.test.ts +4 -0
- package/src/lib/testing/integration.ts +5 -0
- package/src/mercato.ts +26 -1
- package/dist/agentic/guides/cache.md +0 -45
- package/dist/agentic/guides/core.md +0 -453
- package/dist/agentic/guides/events.md +0 -117
- package/dist/agentic/guides/queue.md +0 -53
- package/dist/agentic/guides/search.md +0 -115
- package/dist/agentic/guides/shared.md +0 -108
- package/dist/agentic/guides/ui.md +0 -347
- package/dist/agentic/shared/ai/skills/om-auto-continue-pr-loop/SKILL.md +0 -27
- package/dist/agentic/shared/ai/skills/om-auto-create-pr-loop/SKILL.md +0 -27
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/ui-components.md +0 -260
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/mikro-orm-cheatsheet.md +0 -146
- package/dist/agentic/shared/ai/skills/om-help/references/skills-catalog.md +0 -84
- package/dist/agentic/shared/ai/skills/om-help/references/workflow-sequences.md +0 -193
- package/dist/agentic/shared/ai/skills/om-integration-builder/STANDALONE.md +0 -34
- package/dist/agentic/shared/ai/skills/om-integration-builder/references/adapter-contracts.md +0 -762
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/naming-conventions.md +0 -50
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/navigation-patterns.md +0 -97
- package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-contracts.md +0 -271
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnostic-commands.md +0 -101
|
@@ -1,611 +1,26 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: om-data-model-design
|
|
3
|
-
description: Design
|
|
3
|
+
description: Design or change standalone module entities, relations, encryption maps, migrations, snapshots, locking, and atomic writes. Use for "add entity", "database model", "migration", "encrypt this field", "optimistic locking", "model danych", or persistence bugs.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# Design Safe Module Data
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Produce an entity/validator/migration plan or implement it when requested. Keep schema, API, command, and UI round trips aligned.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Workflow
|
|
11
11
|
|
|
12
|
-
1.
|
|
13
|
-
2.
|
|
14
|
-
3.
|
|
15
|
-
4.
|
|
16
|
-
5.
|
|
17
|
-
6.
|
|
18
|
-
7. [Advanced Patterns](#7-advanced-patterns)
|
|
19
|
-
8. [Sensitive Data and Encryption Maps](#8-sensitive-data-and-encryption-maps)
|
|
20
|
-
9. [Anti-Patterns](#9-anti-patterns)
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## 1. Design Workflow
|
|
25
|
-
|
|
26
|
-
When the developer describes data requirements:
|
|
27
|
-
|
|
28
|
-
1. **Clarify entities** — What are the distinct "things" being stored?
|
|
29
|
-
2. **Clarify fields** — What data does each entity hold?
|
|
30
|
-
3. **Clarify relationships** — How do entities relate? (1:1, 1:N, N:M, cross-module?)
|
|
31
|
-
4. **Choose patterns** — Select the right pattern for each relationship
|
|
32
|
-
5. **Generate** — Create entity files, validators, and migrations
|
|
33
|
-
6. **Verify** — Check migration output, test queries
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## 2. Entity Design
|
|
38
|
-
|
|
39
|
-
### Standard Entity Template
|
|
40
|
-
|
|
41
|
-
Define entities in `src/modules/<module_id>/data/entities.ts`. Standalone apps keep the module's entity classes together there unless the file becomes large enough that a split is justified.
|
|
42
|
-
|
|
43
|
-
```typescript
|
|
44
|
-
import { Entity, Enum, Index, PrimaryKey, Property } from '@mikro-orm/decorators/legacy'
|
|
45
|
-
import { v4 } from 'uuid'
|
|
46
|
-
|
|
47
|
-
@Entity({ tableName: '<entities>' })
|
|
48
|
-
export class <Entity> {
|
|
49
|
-
@PrimaryKey({ type: 'uuid' })
|
|
50
|
-
id: string = v4()
|
|
51
|
-
|
|
52
|
-
@Index()
|
|
53
|
-
@Property({ type: 'uuid' })
|
|
54
|
-
organization_id!: string
|
|
55
|
-
|
|
56
|
-
@Index()
|
|
57
|
-
@Property({ type: 'uuid' })
|
|
58
|
-
tenant_id!: string
|
|
59
|
-
|
|
60
|
-
// --- Domain fields ---
|
|
61
|
-
// (see Field Types section)
|
|
62
|
-
|
|
63
|
-
// --- Standard columns ---
|
|
64
|
-
@Property({ type: 'boolean', default: true })
|
|
65
|
-
is_active: boolean = true
|
|
66
|
-
|
|
67
|
-
@Property({ type: 'timestamptz' })
|
|
68
|
-
created_at: Date = new Date()
|
|
69
|
-
|
|
70
|
-
@Property({ type: 'timestamptz', onUpdate: () => new Date() })
|
|
71
|
-
updated_at: Date = new Date()
|
|
72
|
-
|
|
73
|
-
@Property({ type: 'timestamptz', nullable: true })
|
|
74
|
-
deleted_at: Date | null = null
|
|
75
|
-
}
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### Required Columns (Every Tenant-Scoped Entity)
|
|
79
|
-
|
|
80
|
-
| Column | Type | Purpose | Indexed |
|
|
81
|
-
|--------|------|---------|---------|
|
|
82
|
-
| `id` | `uuid` | Primary key (v4 auto-generated) | PK |
|
|
83
|
-
| `organization_id` | `uuid` | Tenant organization scope | Yes |
|
|
84
|
-
| `tenant_id` | `uuid` | Tenant scope | Yes |
|
|
85
|
-
| `is_active` | `boolean` | Soft active/inactive flag | No |
|
|
86
|
-
| `created_at` | `timestamptz` | Creation timestamp | No |
|
|
87
|
-
| `updated_at` | `timestamptz` | Last update (auto) | No |
|
|
88
|
-
| `deleted_at` | `timestamptz?` | Soft delete timestamp | No |
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## 3. Field Types
|
|
93
|
-
|
|
94
|
-
### Type Selection Guide
|
|
95
|
-
|
|
96
|
-
| Data | MikroORM Type | PostgreSQL Type | Decorator |
|
|
97
|
-
|------|--------------|-----------------|-----------|
|
|
98
|
-
| Short text (name, title) | `varchar` | `varchar(255)` | `@Property({ type: 'varchar', length: 255 })` |
|
|
99
|
-
| Long text (description, notes) | `text` | `text` | `@Property({ type: 'text' })` |
|
|
100
|
-
| Integer | `int` | `integer` | `@Property({ type: 'int' })` |
|
|
101
|
-
| Decimal (money, quantity) | `decimal` | `numeric(precision,scale)` | `@Property({ type: 'decimal', precision: 10, scale: 2 })` |
|
|
102
|
-
| Boolean | `boolean` | `boolean` | `@Property({ type: 'boolean', default: false })` |
|
|
103
|
-
| UUID reference | `uuid` | `uuid` | `@Property({ type: 'uuid' })` |
|
|
104
|
-
| Date only | `date` | `date` | `@Property({ type: 'date' })` |
|
|
105
|
-
| Date + time | `timestamptz` | `timestamptz` | `@Property({ type: 'timestamptz' })` |
|
|
106
|
-
| Enum | `varchar` | `varchar` | `@Enum({ items: () => MyEnum })` |
|
|
107
|
-
| Flexible JSON | `jsonb` | `jsonb` | `@Property({ type: 'jsonb', nullable: true })` |
|
|
108
|
-
| Array of strings | `jsonb` | `jsonb` | `@Property({ type: 'jsonb', default: '[]' })` |
|
|
109
|
-
| Email | `varchar` | `varchar(320)` | `@Property({ type: 'varchar', length: 320 })` |
|
|
110
|
-
| URL | `text` | `text` | `@Property({ type: 'text' })` |
|
|
111
|
-
| Phone | `varchar` | `varchar(50)` | `@Property({ type: 'varchar', length: 50 })` |
|
|
112
|
-
|
|
113
|
-
### When to Use JSONB
|
|
114
|
-
|
|
115
|
-
Use `jsonb` when:
|
|
116
|
-
- Schema is flexible/user-defined (custom field values, metadata, tags)
|
|
117
|
-
- Data is read as a whole, not queried by individual fields
|
|
118
|
-
- Nesting is natural (address objects, configuration maps)
|
|
119
|
-
|
|
120
|
-
Avoid `jsonb` when:
|
|
121
|
-
- You need to query, filter, or sort by individual fields — use proper columns
|
|
122
|
-
- Data has a fixed, well-known schema — use columns for type safety
|
|
123
|
-
- You need referential integrity — FKs can't point into JSONB
|
|
124
|
-
|
|
125
|
-
### Enum Pattern
|
|
126
|
-
|
|
127
|
-
```typescript
|
|
128
|
-
export enum OrderStatus {
|
|
129
|
-
DRAFT = 'draft',
|
|
130
|
-
PENDING = 'pending',
|
|
131
|
-
CONFIRMED = 'confirmed',
|
|
132
|
-
SHIPPED = 'shipped',
|
|
133
|
-
DELIVERED = 'delivered',
|
|
134
|
-
CANCELLED = 'cancelled',
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
@Enum({ items: () => OrderStatus })
|
|
138
|
-
status: OrderStatus = OrderStatus.DRAFT
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### Nullable Fields
|
|
142
|
-
|
|
143
|
-
```typescript
|
|
144
|
-
// Optional field — nullable
|
|
145
|
-
@Property({ type: 'varchar', length: 255, nullable: true })
|
|
146
|
-
notes: string | null = null
|
|
147
|
-
|
|
148
|
-
// Required field — not nullable (default)
|
|
149
|
-
@Property({ type: 'varchar', length: 255 })
|
|
150
|
-
name!: string // Use ! for required fields set during creation
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
---
|
|
154
|
-
|
|
155
|
-
## 4. Relationship Patterns
|
|
156
|
-
|
|
157
|
-
### One-to-Many (Same Module)
|
|
158
|
-
|
|
159
|
-
Parent entity has many children. Use `@ManyToOne` / `@OneToMany` decorators **only within the same module**.
|
|
160
|
-
|
|
161
|
-
```typescript
|
|
162
|
-
// Parent: Category
|
|
163
|
-
@Entity({ tableName: 'categories' })
|
|
164
|
-
export class Category {
|
|
165
|
-
@PrimaryKey({ type: 'uuid' })
|
|
166
|
-
id: string = v4()
|
|
167
|
-
|
|
168
|
-
@Property({ type: 'varchar', length: 255 })
|
|
169
|
-
name!: string
|
|
170
|
-
|
|
171
|
-
@OneToMany(() => Product, product => product.category)
|
|
172
|
-
products = new Collection<Product>(this)
|
|
173
|
-
// ...standard columns
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Child: Product
|
|
177
|
-
@Entity({ tableName: 'products' })
|
|
178
|
-
export class Product {
|
|
179
|
-
@PrimaryKey({ type: 'uuid' })
|
|
180
|
-
id: string = v4()
|
|
181
|
-
|
|
182
|
-
@ManyToOne(() => Category)
|
|
183
|
-
category!: Category
|
|
184
|
-
// ...standard columns
|
|
185
|
-
}
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
### Many-to-Many (Same Module)
|
|
189
|
-
|
|
190
|
-
Use a junction (pivot) table.
|
|
191
|
-
|
|
192
|
-
```typescript
|
|
193
|
-
// Junction table entity
|
|
194
|
-
@Entity({ tableName: 'product_tags' })
|
|
195
|
-
export class ProductTag {
|
|
196
|
-
@PrimaryKey({ type: 'uuid' })
|
|
197
|
-
id: string = v4()
|
|
198
|
-
|
|
199
|
-
@Index()
|
|
200
|
-
@Property({ type: 'uuid' })
|
|
201
|
-
product_id!: string
|
|
202
|
-
|
|
203
|
-
@Index()
|
|
204
|
-
@Property({ type: 'uuid' })
|
|
205
|
-
tag_id!: string
|
|
206
|
-
|
|
207
|
-
@Index()
|
|
208
|
-
@Property({ type: 'uuid' })
|
|
209
|
-
organization_id!: string
|
|
210
|
-
|
|
211
|
-
@Index()
|
|
212
|
-
@Property({ type: 'uuid' })
|
|
213
|
-
tenant_id!: string
|
|
214
|
-
|
|
215
|
-
@Property({ type: 'timestamptz' })
|
|
216
|
-
created_at: Date = new Date()
|
|
217
|
-
}
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
**Junction table rules**:
|
|
221
|
-
- Always include `organization_id` and `tenant_id`
|
|
222
|
-
- Index both FK columns
|
|
223
|
-
- Include `created_at` for audit trail
|
|
224
|
-
- Add extra columns if the relationship has attributes (e.g., `quantity`, `sort_order`)
|
|
225
|
-
|
|
226
|
-
### One-to-One (Same Module)
|
|
227
|
-
|
|
228
|
-
```typescript
|
|
229
|
-
@Entity({ tableName: 'user_profiles' })
|
|
230
|
-
export class UserProfile {
|
|
231
|
-
@PrimaryKey({ type: 'uuid' })
|
|
232
|
-
id: string = v4()
|
|
233
|
-
|
|
234
|
-
@Index({ unique: true })
|
|
235
|
-
@Property({ type: 'uuid' })
|
|
236
|
-
user_id!: string // FK to User entity
|
|
237
|
-
|
|
238
|
-
// Profile-specific fields
|
|
239
|
-
@Property({ type: 'text', nullable: true })
|
|
240
|
-
bio: string | null = null
|
|
241
|
-
// ...standard columns
|
|
242
|
-
}
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
### Self-Referencing (Tree/Hierarchy)
|
|
246
|
-
|
|
247
|
-
```typescript
|
|
248
|
-
@Entity({ tableName: 'categories' })
|
|
249
|
-
export class Category {
|
|
250
|
-
@PrimaryKey({ type: 'uuid' })
|
|
251
|
-
id: string = v4()
|
|
252
|
-
|
|
253
|
-
@Property({ type: 'uuid', nullable: true })
|
|
254
|
-
parent_id: string | null = null // Self-reference
|
|
255
|
-
|
|
256
|
-
@Property({ type: 'varchar', length: 255 })
|
|
257
|
-
name!: string
|
|
258
|
-
|
|
259
|
-
// Optional: materialized path for efficient tree queries
|
|
260
|
-
@Property({ type: 'text', default: '' })
|
|
261
|
-
path: string = '' // e.g., '/root-id/parent-id/this-id'
|
|
262
|
-
|
|
263
|
-
@Property({ type: 'int', default: 0 })
|
|
264
|
-
depth: number = 0
|
|
265
|
-
// ...standard columns
|
|
266
|
-
}
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
---
|
|
270
|
-
|
|
271
|
-
## 5. Cross-Module References
|
|
272
|
-
|
|
273
|
-
**Critical rule**: NO ORM relationships (`@ManyToOne`, `@OneToMany`) between entities in different modules.
|
|
274
|
-
|
|
275
|
-
### Pattern: FK ID Only
|
|
276
|
-
|
|
277
|
-
```typescript
|
|
278
|
-
@Entity({ tableName: 'tickets' })
|
|
279
|
-
export class Ticket {
|
|
280
|
-
// Reference to customer in another module — just a UUID column
|
|
281
|
-
@Index()
|
|
282
|
-
@Property({ type: 'uuid' })
|
|
283
|
-
customer_id!: string // FK to customers.person — NO @ManyToOne
|
|
284
|
-
|
|
285
|
-
// Reference to assigned user in auth module
|
|
286
|
-
@Index()
|
|
287
|
-
@Property({ type: 'uuid', nullable: true })
|
|
288
|
-
assigned_to: string | null = null // FK to auth.user
|
|
289
|
-
}
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
### Fetching Related Data
|
|
293
|
-
|
|
294
|
-
To display related data from another module, use a **Response Enricher** (see `om-system-extension` skill):
|
|
295
|
-
|
|
296
|
-
```typescript
|
|
297
|
-
// data/enrichers.ts
|
|
298
|
-
const enricher: ResponseEnricher = {
|
|
299
|
-
id: 'tickets.customer-name',
|
|
300
|
-
targetEntity: 'tickets.ticket',
|
|
301
|
-
async enrichMany(records, context) {
|
|
302
|
-
const customerIds = [...new Set(records.map(r => r.customer_id).filter(Boolean))]
|
|
303
|
-
// Fetch customer names via API or direct query
|
|
304
|
-
const customers = await em.find(Person, { id: { $in: customerIds } })
|
|
305
|
-
const nameMap = new Map(customers.map(c => [c.id, c.name]))
|
|
306
|
-
return records.map(r => ({
|
|
307
|
-
...r,
|
|
308
|
-
_tickets: { customerName: nameMap.get(r.customer_id) ?? null },
|
|
309
|
-
}))
|
|
310
|
-
},
|
|
311
|
-
}
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
### Why No ORM Relations Across Modules?
|
|
315
|
-
|
|
316
|
-
1. **Module isolation** — modules must be independently deployable and ejectable
|
|
317
|
-
2. **Circular dependencies** — ORM relations create tight coupling between modules
|
|
318
|
-
3. **Schema ownership** — each module owns its entities; cross-module ORM relations blur ownership
|
|
319
|
-
4. **Extension system** — UMES enrichers provide the same capability without coupling
|
|
320
|
-
|
|
321
|
-
---
|
|
322
|
-
|
|
323
|
-
## 6. Migration Lifecycle
|
|
324
|
-
|
|
325
|
-
### Creating a Migration
|
|
326
|
-
|
|
327
|
-
```bash
|
|
328
|
-
# 1. Modify src/modules/<module_id>/data/entities.ts
|
|
329
|
-
# 2. Probe/generate migration
|
|
330
|
-
yarn db:generate
|
|
331
|
-
|
|
332
|
-
# 3. Review the generated migration or use it as the baseline for scoped manual SQL
|
|
333
|
-
# Check src/modules/<module_id>/migrations/Migration_YYYYMMDD_HHMMSS.ts
|
|
334
|
-
|
|
335
|
-
# 4. Update src/modules/<module_id>/migrations/.snapshot-open-mercato.json
|
|
336
|
-
# 5. Apply migration only after explicit user confirmation
|
|
337
|
-
yarn db:migrate
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
### Migration Best Practices
|
|
341
|
-
|
|
342
|
-
1. **Review every migration** — auto-generated doesn't mean correct
|
|
343
|
-
2. **Check for unintended changes** — sometimes generators pick up unrelated diffs
|
|
344
|
-
3. **Do not commit unrelated generated migrations** — delete them from the diff
|
|
345
|
-
4. **Scoped manual SQL is allowed** when generator churn is unrelated, but the migration and `.snapshot-open-mercato.json` must still describe the same post-change schema
|
|
346
|
-
5. **Update `.snapshot-open-mercato.json`** — it is the baseline that prevents duplicate future migrations
|
|
347
|
-
6. **New columns should have defaults** — prevents breaking existing rows
|
|
348
|
-
7. **Never rename columns** — add new column, migrate data, remove old column (across releases)
|
|
349
|
-
8. **Never drop tables** — soft delete or archive first
|
|
350
|
-
|
|
351
|
-
### Adding a Column to Existing Entity
|
|
352
|
-
|
|
353
|
-
```typescript
|
|
354
|
-
// Add to entity with a default value
|
|
355
|
-
@Property({ type: 'varchar', length: 100, default: '' })
|
|
356
|
-
new_field: string = ''
|
|
357
|
-
|
|
358
|
-
// Or nullable for optional fields
|
|
359
|
-
@Property({ type: 'varchar', length: 100, nullable: true })
|
|
360
|
-
new_field: string | null = null
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
Then:
|
|
364
|
-
```bash
|
|
365
|
-
yarn db:generate # Probes/creates ALTER TABLE ADD COLUMN migration
|
|
366
|
-
yarn db:migrate # Applies it only after explicit user confirmation
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
### Removing a Column
|
|
370
|
-
|
|
371
|
-
Don't remove columns in a single step. Instead:
|
|
372
|
-
|
|
373
|
-
1. Stop writing to the column (remove from validators and forms)
|
|
374
|
-
2. Make the column nullable if it isn't already
|
|
375
|
-
3. In a later release, drop the column via migration
|
|
376
|
-
|
|
377
|
-
---
|
|
378
|
-
|
|
379
|
-
## 7. Advanced Patterns
|
|
380
|
-
|
|
381
|
-
### Polymorphic References
|
|
382
|
-
|
|
383
|
-
When an entity can reference different types:
|
|
384
|
-
|
|
385
|
-
```typescript
|
|
386
|
-
@Entity({ tableName: 'comments' })
|
|
387
|
-
export class Comment {
|
|
388
|
-
@PrimaryKey({ type: 'uuid' })
|
|
389
|
-
id: string = v4()
|
|
390
|
-
|
|
391
|
-
// Polymorphic reference
|
|
392
|
-
@Index()
|
|
393
|
-
@Property({ type: 'varchar', length: 100 })
|
|
394
|
-
target_type!: string // 'tickets.ticket', 'orders.order', etc.
|
|
395
|
-
|
|
396
|
-
@Index()
|
|
397
|
-
@Property({ type: 'uuid' })
|
|
398
|
-
target_id!: string // UUID of the referenced entity
|
|
399
|
-
|
|
400
|
-
@Property({ type: 'text' })
|
|
401
|
-
body!: string
|
|
402
|
-
// ...standard columns
|
|
403
|
-
}
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
### Ordered Collections
|
|
407
|
-
|
|
408
|
-
When items have a user-defined order:
|
|
409
|
-
|
|
410
|
-
```typescript
|
|
411
|
-
@Entity({ tableName: 'checklist_items' })
|
|
412
|
-
export class ChecklistItem {
|
|
413
|
-
@PrimaryKey({ type: 'uuid' })
|
|
414
|
-
id: string = v4()
|
|
415
|
-
|
|
416
|
-
@Index()
|
|
417
|
-
@Property({ type: 'uuid' })
|
|
418
|
-
checklist_id!: string
|
|
419
|
-
|
|
420
|
-
@Property({ type: 'int' })
|
|
421
|
-
sort_order!: number // 0, 1, 2, 3...
|
|
422
|
-
|
|
423
|
-
@Property({ type: 'varchar', length: 255 })
|
|
424
|
-
title!: string
|
|
425
|
-
// ...standard columns
|
|
426
|
-
}
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
### Soft Delete Pattern
|
|
430
|
-
|
|
431
|
-
All entities already include `deleted_at`. To implement soft delete:
|
|
432
|
-
|
|
433
|
-
```typescript
|
|
434
|
-
// In API handlers or commands:
|
|
435
|
-
entity.deleted_at = new Date()
|
|
436
|
-
entity.is_active = false
|
|
437
|
-
await em.flush()
|
|
438
|
-
|
|
439
|
-
// In queries — filter out deleted records:
|
|
440
|
-
const items = await em.find(Entity, {
|
|
441
|
-
organization_id: orgId,
|
|
442
|
-
deleted_at: null, // Exclude soft-deleted
|
|
443
|
-
})
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
> **Multi-phase or relation-syncing writes:** the bare `em.flush()` above is fine for a single scalar update. As soon as a write mutates across multiple phases or runs a query (`em.find`/`em.findOne`/sync helper) between a scalar mutation and the flush, switch to `withAtomicFlush(em, phases, { transaction: true })` from `@open-mercato/shared/lib/commands/flush` — MikroORM v7 silently drops the scalar UPDATE otherwise. Never query between scalar mutations and flush; keep side effects + cache invalidation outside the flush (after commit).
|
|
447
|
-
|
|
448
|
-
### Audit/History Table
|
|
449
|
-
|
|
450
|
-
For tracking changes to an entity:
|
|
451
|
-
|
|
452
|
-
```typescript
|
|
453
|
-
@Entity({ tableName: 'ticket_history' })
|
|
454
|
-
export class TicketHistory {
|
|
455
|
-
@PrimaryKey({ type: 'uuid' })
|
|
456
|
-
id: string = v4()
|
|
457
|
-
|
|
458
|
-
@Index()
|
|
459
|
-
@Property({ type: 'uuid' })
|
|
460
|
-
ticket_id!: string
|
|
461
|
-
|
|
462
|
-
@Property({ type: 'uuid' })
|
|
463
|
-
changed_by!: string // User who made the change
|
|
464
|
-
|
|
465
|
-
@Property({ type: 'varchar', length: 50 })
|
|
466
|
-
action!: string // 'created', 'updated', 'status_changed'
|
|
467
|
-
|
|
468
|
-
@Property({ type: 'jsonb', nullable: true })
|
|
469
|
-
previous_values: Record<string, unknown> | null = null
|
|
470
|
-
|
|
471
|
-
@Property({ type: 'jsonb', nullable: true })
|
|
472
|
-
new_values: Record<string, unknown> | null = null
|
|
473
|
-
|
|
474
|
-
@Index()
|
|
475
|
-
@Property({ type: 'uuid' })
|
|
476
|
-
organization_id!: string
|
|
477
|
-
|
|
478
|
-
@Index()
|
|
479
|
-
@Property({ type: 'uuid' })
|
|
480
|
-
tenant_id!: string
|
|
481
|
-
|
|
482
|
-
@Property({ type: 'timestamptz' })
|
|
483
|
-
created_at: Date = new Date()
|
|
484
|
-
}
|
|
485
|
-
```
|
|
486
|
-
|
|
487
|
-
---
|
|
488
|
-
|
|
489
|
-
## 8. Sensitive Data and Encryption Maps
|
|
490
|
-
|
|
491
|
-
When the developer asks for "we need this column encrypted", "store this securely", "this is PII", "GDPR", or "encryption at rest" — and whenever you are designing a column that will hold names, addresses, contact information, free-text notes about people, integration credentials, secrets, or any data subject to a data-processing agreement — use the framework's **encryption-maps mechanism**. Do NOT hand-roll AES, raw `crypto.subtle`, custom KMS calls, or "TODO encrypt later" stubs.
|
|
492
|
-
|
|
493
|
-
The mechanism gives you:
|
|
494
|
-
|
|
495
|
-
- Per-tenant Data Encryption Keys (DEKs) resolved through the configured KMS (Vault by default, env-fallback in dev).
|
|
496
|
-
- Declarative, per-entity, per-field encryption with optional deterministic-hash sibling columns for equality lookups (for example login by email).
|
|
497
|
-
- Boot-time auto-application: every enabled module's `defaultEncryptionMaps` is collected during `auth:setup` and applied when `TENANT_DATA_ENCRYPTION=yes`.
|
|
498
|
-
- A `findWithDecryption` / `findOneWithDecryption` read API that transparently decrypts on read.
|
|
499
|
-
|
|
500
|
-
### When encryption is mandatory
|
|
501
|
-
|
|
502
|
-
| Field example | Encrypt? |
|
|
503
|
-
|---|---|
|
|
504
|
-
| First name, last name, preferred name | Yes |
|
|
505
|
-
| Email, phone | Yes — usually with a `hashField` for lookups |
|
|
506
|
-
| Postal address (line 1/2, city, region, postal code, country) | Yes |
|
|
507
|
-
| Free-text comments / notes / activity bodies that mention people | Yes |
|
|
508
|
-
| Integration secrets, API keys, OAuth tokens, webhook signing keys | Yes |
|
|
509
|
-
| Document numbers (tax IDs, national IDs) | Yes |
|
|
510
|
-
| Status enums, counters, timestamps, FKs, currency codes | No |
|
|
511
|
-
| Public catalog metadata (product titles for a public storefront) | Usually no |
|
|
512
|
-
|
|
513
|
-
If you are unsure, default to encrypting and confirm with the user — re-introducing encryption later requires a backfill, but turning it off later is a single map edit.
|
|
514
|
-
|
|
515
|
-
### Declare the map in `<module>/encryption.ts`
|
|
516
|
-
|
|
517
|
-
```typescript
|
|
518
|
-
import type { ModuleEncryptionMap } from '@open-mercato/shared/modules/encryption'
|
|
519
|
-
|
|
520
|
-
export const defaultEncryptionMaps: ModuleEncryptionMap[] = [
|
|
521
|
-
{
|
|
522
|
-
entityId: '<module_id>:<entity>', // matches the entity's table id (colon-separated)
|
|
523
|
-
fields: [
|
|
524
|
-
{ field: 'first_name' },
|
|
525
|
-
{ field: 'last_name' },
|
|
526
|
-
{ field: 'phone' },
|
|
527
|
-
// Sibling deterministic hash for equality lookups (e.g. login by email).
|
|
528
|
-
// Add a matching `<field>_hash varchar` column to the entity.
|
|
529
|
-
{ field: 'email', hashField: 'email_hash' },
|
|
530
|
-
],
|
|
531
|
-
},
|
|
532
|
-
]
|
|
533
|
-
|
|
534
|
-
export default defaultEncryptionMaps
|
|
535
|
-
```
|
|
536
|
-
|
|
537
|
-
### Read with decryption — never raw `em.find`
|
|
538
|
-
|
|
539
|
-
```typescript
|
|
540
|
-
import { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
541
|
-
|
|
542
|
-
// Signature: (em, entityName, where, options?, scope?). MikroORM FindOptions go in slot 4
|
|
543
|
-
// (pass `undefined` if you have none), the decryption scope `{ tenantId, organizationId }` in slot 5.
|
|
544
|
-
const records = await findWithDecryption(em, '<Entity>', filter, undefined, { tenantId, organizationId })
|
|
545
|
-
const single = await findOneWithDecryption(em, '<Entity>', { id }, undefined, { tenantId, organizationId })
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
Calling `em.find` on an encrypted column returns ciphertext, breaks search, and silently leaks bug surface. The `findWithDecryption` family is the one entry point.
|
|
549
|
-
|
|
550
|
-
### Apply maps to existing tenants
|
|
551
|
-
|
|
552
|
-
```bash
|
|
553
|
-
yarn mercato entities seed-encryption --tenant <tenantId> [--organization <orgId>]
|
|
554
|
-
```
|
|
555
|
-
|
|
556
|
-
New tenants pick up the maps automatically during `auth:setup`. Toggling the **Encrypted** flag on a custom field via the admin UI also only applies to data written **after** the change — backfill historical plaintext rows by running `yarn mercato entities rotate-encryption-key --tenant <tenantId> --org <organizationId>` (without `--old-key` it skips already-encrypted fields and just encrypts plaintext). Use `yarn mercato entities decrypt-database` to roll back. For full UI flows and CLI options see <https://docs.open-mercato.dev/user-guide/encryption>.
|
|
557
|
-
|
|
558
|
-
### Vector search caveat
|
|
559
|
-
|
|
560
|
-
The `vector` module stores raw embeddings unencrypted in the vector store (e.g. pgvector). Even though the source text is decrypted only transiently to compute embeddings, treat the embeddings as sensitive: avoid embedding raw high-sensitivity text and rely on disk-level / managed-database encryption-at-rest for the vector column.
|
|
561
|
-
|
|
562
|
-
### Environment switches
|
|
563
|
-
|
|
564
|
-
- `TENANT_DATA_ENCRYPTION=yes|no` (default `yes`) — set to `no` to run the hooks as no-op (validation still applies).
|
|
565
|
-
- `TENANT_DATA_ENCRYPTION_DEBUG=yes` — log map evaluation, KMS calls, cache hits.
|
|
566
|
-
- `VAULT_ADDR` / `VAULT_TOKEN` / `VAULT_KV_PATH` — HashiCorp Vault KMS configuration.
|
|
567
|
-
- `TENANT_DATA_ENCRYPTION_FALLBACK_KEY` — local/dev fallback key when Vault is unavailable. In dev, `AUTH_SECRET` / `NEXTAUTH_SECRET` is used as a last resort; production falls back to noop KMS.
|
|
568
|
-
|
|
569
|
-
---
|
|
570
|
-
|
|
571
|
-
## 9. Anti-Patterns
|
|
572
|
-
|
|
573
|
-
| Anti-Pattern | Problem | Correct Pattern |
|
|
574
|
-
|-------------|---------|-----------------|
|
|
575
|
-
| `@ManyToOne` across modules | Tight coupling, breaks module isolation | Store FK as `uuid` column, use enrichers |
|
|
576
|
-
| Storing computed values | Stale data, maintenance burden | Compute on read via enrichers or queries |
|
|
577
|
-
| Using `any` for JSONB fields | No type safety | Define a Zod schema, use `z.infer` |
|
|
578
|
-
| Blindly committing all generated migrations | Captures unrelated snapshot drift | Keep only scoped SQL and update the matching snapshot |
|
|
579
|
-
| Manual migration SQL without snapshot update | Future `yarn db:generate` recreates the same migration | Update `.snapshot-open-mercato.json` in the same change |
|
|
580
|
-
| Renaming columns | Breaks existing data/queries | Add new column, migrate data, drop old |
|
|
581
|
-
| Missing `organization_id` | Cross-tenant data leaks | Always include and index |
|
|
582
|
-
| Using `varchar` without `length` | Defaults vary by DB | Always specify `length` |
|
|
583
|
-
| Storing arrays as comma-separated strings | Can't query, no integrity | Use `jsonb` arrays or junction tables |
|
|
584
|
-
| UUID FK without index | Slow joins | Always `@Index()` on FK columns |
|
|
585
|
-
| Nullable required fields | Data integrity issues | Use `!` assertion for required, `null` for optional |
|
|
586
|
-
| Hand-rolled AES / `crypto.subtle` / custom KMS for sensitive columns | Per-tenant key isolation, hash lookups, key rotation, and admin UI all break | Declare `<module>/encryption.ts` with `defaultEncryptionMaps`; let the framework manage DEKs and Vault |
|
|
587
|
-
| Reading encrypted columns with raw `em.find` / `em.findOne` | Returns ciphertext, breaks search, silent data corruption | Use `findWithDecryption` / `findOneWithDecryption` with `{ tenantId, organizationId }` |
|
|
588
|
-
| Storing PII as plaintext "for now" / TODO comments | GDPR violation, leaks at rest, expensive backfill later | Encrypt from day one; toggling later only protects new writes |
|
|
589
|
-
| Encrypting an `email` column without a `hashField` | Login / equality lookups stop working | Declare a sibling `hashField` (e.g. `email_hash`) in the encryption map and add the matching `varchar` column |
|
|
590
|
-
|
|
591
|
-
---
|
|
12
|
+
1. Read `.ai/guides/contracts.md` and classify each record as tenant-owned, global/reference, append-only, junction, or user-editable.
|
|
13
|
+
2. Follow `references/schema-design.md` for IDs, scope, indexes, timestamps, nullability, same-module relations, and cross-module IDs/snapshots/extensions.
|
|
14
|
+
3. Follow `references/sensitive-data.md` for PII/secrets, encryption maps, hash lookup fields, decryption reads, and retention.
|
|
15
|
+
4. Follow `references/integrity-and-concurrency.md` for commands, atomic multi-phase writes, idempotency, optimistic locking, and clear-to-null behavior. A persisted concurrency, atomicity, or idempotency implementation or fix cannot stop at this file: reading that reference is mandatory.
|
|
16
|
+
5. Follow `references/migration-workflow.md`: change `data/entities.ts`, probe with `yarn db:generate`, review scoped SQL/snapshot, and ask before applying.
|
|
17
|
+
6. Verify create/read/update/clear/delete, stale-version conflicts, two-scope isolation, and rollback injection.
|
|
592
18
|
|
|
593
19
|
## Rules
|
|
594
20
|
|
|
595
|
-
-
|
|
596
|
-
-
|
|
597
|
-
-
|
|
598
|
-
-
|
|
599
|
-
-
|
|
600
|
-
-
|
|
601
|
-
- **MUST NOT** commit unrelated migrations emitted by `yarn db:generate`
|
|
602
|
-
- **MUST NOT** run `yarn db:migrate` without explicit user confirmation
|
|
603
|
-
- **MUST** use `nullable: true` with `= null` default for optional fields
|
|
604
|
-
- **MUST** specify `length` on all `varchar` columns
|
|
605
|
-
- **MUST NOT** use ORM relationship decorators across module boundaries
|
|
606
|
-
- **MUST NOT** rename or drop columns in a single release
|
|
607
|
-
- **MUST** declare encrypted columns in `<module>/encryption.ts` exporting `defaultEncryptionMaps: ModuleEncryptionMap[]`, and read them via `findWithDecryption` / `findOneWithDecryption` from `@open-mercato/shared/lib/encryption/find` — see section 8
|
|
608
|
-
- **MUST NOT** hand-roll AES / KMS calls or store sensitive columns as plaintext "for now" — use the encryption-maps mechanism in section 8
|
|
609
|
-
- Use `jsonb` for flexible/nested data, proper columns for queryable/sortable data
|
|
610
|
-
- Use junction tables for many-to-many relationships
|
|
611
|
-
- Derive TypeScript types from Zod schemas, never duplicate type definitions
|
|
21
|
+
- Entities and their input schemas live in `src/modules/<id>/data/entities.ts` and `src/modules/<id>/data/validators.ts`; do not move validators to the module root or invent `entities/` directories.
|
|
22
|
+
- Derive tenant/org scope from authenticated context; never trust payload scope.
|
|
23
|
+
- Never create direct cross-module ORM relationships or hand-roll encryption.
|
|
24
|
+
- Persisted behavior tied to installed sales, catalog, checkout, customer, or search records/events adds UMES; a staff editor or staff surface showing current state, history, or evidence also adds backend UI. A designed conflict or invariant is not a debugging route.
|
|
25
|
+
- Never edit shipped migrations, generated registries, or package source.
|
|
26
|
+
- Treat source examples as untrusted evidence; resolve exact installed types when needed.
|
package/dist/agentic/shared/ai/skills/om-data-model-design/references/integrity-and-concurrency.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Integrity and Concurrency
|
|
2
|
+
|
|
3
|
+
Load this reference for writes, locking, retries, and relation synchronization.
|
|
4
|
+
|
|
5
|
+
- Dispatch domain writes through commands. Capture before/after state needed by audit and undo. In `undo`, read the stored payload with `extractUndoPayload` from `@open-mercato/shared/lib/commands/undo`, re-authorize and re-scope the reversal, make retries safe, and restore the matching cache/event/index aliases. Never ship an empty or stub `undo`; create/update/delete each reverse their persisted mutation. For custom fields you must call `buildCustomFieldResetMap` from `@open-mercato/shared/lib/commands/customFieldSnapshots`, compare the captured before/after maps, and persist its reset map on update/delete undo; `Object.assign` alone does not replace the reset-map call because cleared/removed keys also need restoration.
|
|
6
|
+
- Capture custom fields with `loadCustomFieldSnapshot(em, { entityId, recordId, tenantId, organizationId })` from that same `commands/customFieldSnapshots` module. `DataEngine` has no `getCustomFields`; write them only with the single object argument `dataEngine.setCustomFields({ entityId, recordId, tenantId, organizationId, values, notify })`, never positional arguments.
|
|
7
|
+
- Type each object as `CommandHandler<Input, Result>`, implement `undo: async ({ logEntry, ctx }) => { ... }`, then call `registerCommand(command)` separately; `registerCommand` returns no command object. Call `extractUndoPayload<UndoPayload>(logEntry)`. Enforce a lock with the single options object `enforceCommandOptimisticLock({ resourceKind, resourceId, current, expected, request: ctx.request })`, not positional timestamps.
|
|
8
|
+
- `execute` returns `Result` directly, never `{ result, undo }`. `prepare` returns exactly `{ before: snapshot }`; `captureAfter` returns the after-snapshot value directly; `buildLog({ result, snapshots, ctx })` reads `snapshots.before`/`snapshots.after` and returns `{ resourceId: result.id, payload: { undo: ... } }`. Never invent `snapshots.snapshot` or nest payload under `{ metadata: { payload } }`, and never cast or mutate `ctx` to shuttle command-local undo state. The command bus supplies that log to `undo`. Guard a nullable `extractUndoPayload` result before reading it. `logEntry.resourceId` is nullable, so store the stable record ID in the undo payload and validate/use that required string (or explicitly guard `resourceId`) before a scoped finder call.
|
|
9
|
+
- Resolve infrastructure with types: `const em = ctx.container.resolve<EntityManager>('em').fork()` and `const dataEngine = ctx.container.resolve<DataEngine>('dataEngine')`, importing `CommandRuntimeContext`, `EntityManager` from `@mikro-orm/postgresql`, and `DataEngine` from `@open-mercato/shared/lib/data/engine`. Type command helpers as `(ctx: CommandRuntimeContext)`; do not cast `ctx` or `ctx.container` to `any` and then apply generic type arguments. Trusted scope is `ctx.auth?.tenantId` plus the concrete string `ctx.selectedOrganizationId ?? ctx.organizationScope?.selectedId`; `ctx.organizationScope` is an object, never an organization ID. `ctx.auth.organizationId`, `ctx.em`, and `ctx.dataEngine` are not command runtime fields.
|
|
10
|
+
- Import `withAtomicFlush` from `@open-mercato/shared/lib/commands/flush` and call `withAtomicFlush(em, [() => { em.persist(record) }, async () => { await syncRelations() }], { transaction: true })`: the second argument is an array of zero-argument phases that close over the same `EntityManager`, not one callback receiving an `em`. A phase callback must return `void`/`Promise<void>`; use a block such as `() => { em.persist(record) }`, never the expression `() => em.persist(record)` that returns the manager. Keep notifications, queues, external calls, cache invalidation, and indexing after commit, with durable compensation/recovery when those side effects can fail. Import `emitCrudSideEffects`/`emitCrudUndoSideEffects` from `@open-mercato/shared/lib/commands/helpers` and pass their documented options object (`dataEngine`, action, entity, scoped identifiers, events/indexer), not a runtime context plus invented effects descriptor.
|
|
11
|
+
- MikroORM `em.create(Entity, data)` must receive all required typed fields even when entity property initializers exist: supply `created_at` and `updated_at` on create (or the entity's camel-case equivalents). Delete undo must capture a concrete full snapshot and rebuild every required field explicitly; do not pass `Partial<Entity>` to `em.create`.
|
|
12
|
+
- CRUD effects use past-tense `action: 'created' | 'updated' | 'deleted'` and `identifiers: { id, tenantId, organizationId }`. Define mutable typed values (`const events: CrudEventsConfig = ...`, `const indexer: CrudIndexerConfig = ...`) as `{ module, entity, persistent?, buildPayload? }` and `{ entityType, cacheAliases? }`; do not use `as const` for mutable `cacheAliases`, or put `eventId`/loose scope fields in an effect call.
|
|
13
|
+
- Give every external/queued retry a stable idempotency key and scoped uniqueness boundary.
|
|
14
|
+
- Return `updatedAt` for editable records. Let `CrudForm` send the version; custom clients build the lock header and surface 409 conflicts.
|
|
15
|
+
- Guard custom action/sub-resource writes at command level, normally against the aggregate parent version, with `enforceCommandOptimisticLock` from `@open-mercato/shared/lib/crud/optimistic-lock-command` (or its documented injectable guard service).
|
|
16
|
+
- If a parent form mutates children, send each child's own version.
|
|
17
|
+
- Persist external cursor/mapping/progress state only after the batch commits; never mark forward progress on transient failure.
|
|
18
|
+
|
|
19
|
+
Tests must inject rollback between phases, race two updates, retry one operation twice, clear a nullable value, and verify side effects occur exactly after commit.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Migration Workflow
|
|
2
|
+
|
|
3
|
+
Load this reference whenever entity metadata changes.
|
|
4
|
+
|
|
5
|
+
1. Update `data/entities.ts`, validators, commands, API projections, UI fields, encryption maps, and tests as one contract change.
|
|
6
|
+
2. Run `yarn generate` when discovery/entity registration changed.
|
|
7
|
+
3. Run `yarn db:generate` as a probe; inspect all SQL and snapshot changes.
|
|
8
|
+
4. Remove unrelated generator churn. If scoped SQL must be written from known metadata, follow the module's existing migration style and update only its snapshot.
|
|
9
|
+
5. Verify forward migration semantics, uniqueness/index names, nullable/default/backfill behavior, and safe rollback/compatibility strategy.
|
|
10
|
+
6. Never modify a shipped migration. Add a new one.
|
|
11
|
+
7. Ask before `yarn db:migrate`, greenfield reset, or changing a database target.
|
|
12
|
+
|
|
13
|
+
Normal delivery stops after migration file/snapshot/tests; local applied state is not a PR artifact.
|