@open-mercato/cli 0.6.7 → 0.6.8-canary.320.1.bf889f49e4
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 +77 -4
- package/AGENTS.md +6 -1
- package/build.mjs +68 -11
- 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 +86 -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 +116757 -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 +482 -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 +305 -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 +149 -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 +168 -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 +687 -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 +372 -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 +469 -47
- package/dist/agentic/guides/modules/workflows.md +302 -43
- package/dist/agentic/guides/reference-module-facts.json +6942 -0
- package/dist/agentic/guides/reference-modules/example.md +462 -0
- package/dist/agentic/guides/spec-delivery.md +56 -0
- package/dist/agentic/guides/testing-debugging.md +80 -0
- package/dist/agentic/guides/upstream/AGENTS.md +300 -0
- package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +334 -0
- package/dist/agentic/guides/upstream/manifest.json +8 -0
- package/dist/agentic/shared/AGENTS.md.template +120 -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 +59 -0
- package/dist/agentic/shared/ai/harness/cases.json +20596 -0
- package/dist/agentic/shared/ai/harness/cases.schema.json +196 -0
- package/dist/agentic/shared/ai/harness/design-system-inventory.json +6326 -0
- package/dist/agentic/shared/ai/harness/fixtures/index.json +51 -0
- package/dist/agentic/shared/ai/harness/fixtures/seeds.json +166 -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/knowledge-change.schema.json +228 -0
- package/dist/agentic/shared/ai/harness/release-matrix.json +163 -0
- package/dist/agentic/shared/ai/harness/release-result.schema.json +219 -0
- package/dist/agentic/shared/ai/harness/result.schema.json +136 -0
- package/dist/agentic/shared/ai/harness/routing-response.schema.json +22 -0
- package/dist/agentic/shared/ai/harness/source-link-inventory.json +1706 -0
- package/dist/agentic/shared/ai/harness/target-validation-result.schema.json +73 -0
- package/dist/agentic/shared/ai/harness/validators.json +82 -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/harness/writable-spec-oracles.mjs +602 -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 +21 -241
- package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/crud-surfaces.md +31 -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 +15 -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 +17 -601
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/integrity-and-concurrency.md +21 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/migration-workflow.md +15 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/schema-design.md +16 -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 +15 -310
- package/dist/agentic/shared/ai/skills/om-eject-and-customize/references/decision-and-procedure.md +19 -0
- package/dist/agentic/shared/ai/skills/om-evolve-harness/SKILL.md +31 -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/knowledge-change.md +133 -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 +25 -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 +25 -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 +32 -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 +14 -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 +40 -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 +20 -843
- package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-branches.md +42 -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 +43 -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 +17 -467
- package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnosis-map.md +29 -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/2026-08-06-reference-module-activation.md +248 -0
- 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 +224 -0
- package/dist/agentic/shared/scripts/check-lessons.mjs +178 -0
- package/dist/agentic/shared/scripts/evaluate-agent-harness.mjs +3812 -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/agentic/shared/scripts/validate-knowledge-change.mjs +1172 -0
- package/dist/bin.js +32 -34
- 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/cli-bootstrap-mode.js +32 -0
- package/dist/lib/cli-bootstrap-mode.js.map +7 -0
- package/dist/lib/dev-supervisor-manifest.js +105 -0
- package/dist/lib/dev-supervisor-manifest.js.map +7 -0
- package/dist/lib/generate-watch-events.js +15 -0
- package/dist/lib/generate-watch-events.js.map +2 -2
- package/dist/lib/generators/entity-ids.js +11 -24
- package/dist/lib/generators/entity-ids.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-di.js +10 -23
- package/dist/lib/generators/module-di.js.map +2 -2
- package/dist/lib/generators/module-entities.js +10 -23
- package/dist/lib/generators/module-entities.js.map +2 -2
- package/dist/lib/generators/module-extension-facts.js +1882 -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 +64 -7
- package/dist/lib/generators/module-facts-discovery.js.map +2 -2
- package/dist/lib/generators/module-facts.js +1913 -52
- package/dist/lib/generators/module-facts.js.map +3 -3
- package/dist/lib/generators/module-override-targets.js +712 -0
- package/dist/lib/generators/module-override-targets.js.map +7 -0
- package/dist/lib/generators/module-registry.js +405 -36
- package/dist/lib/generators/module-registry.js.map +2 -2
- package/dist/lib/generators/route-manifest-shards.js +170 -0
- package/dist/lib/generators/route-manifest-shards.js.map +7 -0
- package/dist/lib/in-process-generate-watcher.js +12 -3
- package/dist/lib/in-process-generate-watcher.js.map +2 -2
- package/dist/lib/load-env.js +24 -0
- package/dist/lib/load-env.js.map +7 -0
- package/dist/lib/queue-worker-supervisor.js.map +2 -2
- package/dist/lib/resolver.js +28 -3
- 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 -2
- package/dist/lib/testing/integration.js.map +2 -2
- package/dist/lib/utils.js +15 -8
- package/dist/lib/utils.js.map +2 -2
- package/dist/mercato.js +35 -9
- package/dist/mercato.js.map +2 -2
- package/package.json +13 -11
- package/src/__tests__/mercato.test.ts +144 -0
- package/src/bin.ts +60 -42
- 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__/cli-bootstrap-mode.test.ts +22 -0
- package/src/lib/__tests__/content-stable-writes.test.ts +74 -0
- package/src/lib/__tests__/dev-supervisor-manifest.test.ts +131 -0
- package/src/lib/__tests__/generate-watch-events.test.ts +76 -0
- package/src/lib/__tests__/in-process-generate-watcher.test.ts +52 -1
- 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__/queue-worker-supervisor.test.ts +33 -0
- package/src/lib/__tests__/resolver.enterprise.test.ts +50 -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/cli-bootstrap-mode.ts +31 -0
- package/src/lib/dev-supervisor-manifest.ts +142 -0
- package/src/lib/generate-watch-events.ts +14 -0
- package/src/lib/generators/__tests__/activation-adapter-coverage.test.ts +92 -0
- package/src/lib/generators/__tests__/entity-ids.test.ts +32 -0
- package/src/lib/generators/__tests__/helpers/override-target-diagnostic-fixtures.ts +150 -0
- package/src/lib/generators/__tests__/module-extension-activations.test.ts +762 -0
- package/src/lib/generators/__tests__/module-extension-facts.test.ts +603 -0
- package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +139 -2
- package/src/lib/generators/__tests__/module-facts.computed-di-token.test.ts +88 -0
- 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.example-fact-coverage.test.ts +414 -0
- package/src/lib/generators/__tests__/module-facts.extension-hosts.test.ts +108 -0
- package/src/lib/generators/__tests__/module-facts.local-reference.test.ts +393 -0
- package/src/lib/generators/__tests__/module-facts.owned-contracts.test.ts +561 -0
- package/src/lib/generators/__tests__/module-facts.search-entity-id.test.ts +151 -0
- package/src/lib/generators/__tests__/module-facts.source-links.test.ts +160 -0
- package/src/lib/generators/__tests__/module-override-targets.diagnostic-wiring.test.ts +92 -0
- package/src/lib/generators/__tests__/module-override-targets.roundtrip.test.ts +149 -0
- package/src/lib/generators/__tests__/module-override-targets.test.ts +328 -0
- package/src/lib/generators/__tests__/module-override-targets.unknown-mode.test.ts +117 -0
- package/src/lib/generators/__tests__/module-subset.test.ts +266 -0
- package/src/lib/generators/__tests__/output-snapshots.test.ts +3 -3
- package/src/lib/generators/__tests__/route-manifest-shards.test.ts +62 -0
- package/src/lib/generators/entity-ids.ts +10 -26
- package/src/lib/generators/extensions/injection-widgets.ts +51 -0
- package/src/lib/generators/module-di.ts +9 -25
- package/src/lib/generators/module-entities.ts +9 -25
- package/src/lib/generators/module-extension-facts.ts +2472 -0
- package/src/lib/generators/module-fact-sources.ts +108 -0
- package/src/lib/generators/module-facts-discovery.ts +104 -7
- package/src/lib/generators/module-facts.ts +2747 -138
- package/src/lib/generators/module-override-targets.ts +907 -0
- package/src/lib/generators/module-registry.ts +448 -35
- package/src/lib/generators/route-manifest-shards.ts +146 -0
- package/src/lib/in-process-generate-watcher.ts +13 -4
- package/src/lib/load-env.ts +30 -0
- package/src/lib/queue-worker-supervisor.ts +7 -2
- package/src/lib/resolver.ts +34 -3
- 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 -2
- package/src/lib/utils.ts +24 -16
- package/src/mercato.ts +46 -9
- 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
package/src/mercato.ts
CHANGED
|
@@ -43,6 +43,7 @@ import { acquireServerStartLock } from './lib/server-start-lock'
|
|
|
43
43
|
import { assertSingleInstanceStrategies } from './lib/single-instance-strategy-guard'
|
|
44
44
|
import { createDevEnvReloader, watchDevEnvFiles } from './lib/dev-env-reload'
|
|
45
45
|
import { quotePostgresIdentifier } from './lib/db/identifiers'
|
|
46
|
+
import { getRegisteredDevSupervisorManifest } from './lib/dev-supervisor-manifest'
|
|
46
47
|
// Lazy-imported to avoid pulling in `testcontainers` (devDependency) at startup
|
|
47
48
|
const lazyIntegration = () => import('./lib/testing/integration')
|
|
48
49
|
import type { ChildProcess } from 'node:child_process'
|
|
@@ -793,7 +794,7 @@ async function runGeneratorSuiteWithStructuralInvalidation(quiet: boolean): Prom
|
|
|
793
794
|
* watchers. Filesystem events mark the tree dirty; the existing full content
|
|
794
795
|
* checksum remains the final authority and the fallback when watching fails.
|
|
795
796
|
*/
|
|
796
|
-
async function createGenerateWatchRuntime() {
|
|
797
|
+
async function createGenerateWatchRuntime(quiet = false) {
|
|
797
798
|
const [
|
|
798
799
|
{ createResolver },
|
|
799
800
|
{ calculateGenerateWatchStructureChecksum },
|
|
@@ -824,6 +825,9 @@ async function createGenerateWatchRuntime() {
|
|
|
824
825
|
return calculateGenerateWatchStructureChecksum(collectWatchState())
|
|
825
826
|
},
|
|
826
827
|
changeSignal: createGenerateWatchChangeSignal({
|
|
828
|
+
onSkippedDirectory: quiet
|
|
829
|
+
? undefined
|
|
830
|
+
: (directory) => console.log(`[generate:watch] Skipping missing watch directory: ${directory}`),
|
|
827
831
|
getWatchTargets: () => {
|
|
828
832
|
const state = collectWatchState()
|
|
829
833
|
const targets: Array<{ directory: string; recursive: boolean; fileName?: string }> = [{
|
|
@@ -867,7 +871,10 @@ async function buildAllModules(): Promise<Module[]> {
|
|
|
867
871
|
export async function run(argv = process.argv) {
|
|
868
872
|
const [, , ...parts] = argv
|
|
869
873
|
const [first, second, ...remaining] = parts
|
|
870
|
-
await ensureEnvLoaded({
|
|
874
|
+
await ensureEnvLoaded({
|
|
875
|
+
createIfMissing: first !== 'deploy' && first !== 'telemetry',
|
|
876
|
+
quiet: first === 'deploy' || first === 'telemetry',
|
|
877
|
+
})
|
|
871
878
|
|
|
872
879
|
// Handle init command directly
|
|
873
880
|
if (first === 'init') {
|
|
@@ -1332,6 +1339,20 @@ export async function run(argv = process.argv) {
|
|
|
1332
1339
|
return exitCode
|
|
1333
1340
|
}
|
|
1334
1341
|
|
|
1342
|
+
// Handle telemetry command (bootstrap-free) — wires @open-mercato/telemetry
|
|
1343
|
+
// into an app scaffolded before telemetry existed.
|
|
1344
|
+
if (first === 'telemetry') {
|
|
1345
|
+
if (second === 'init') {
|
|
1346
|
+
const { runTelemetryInit } = await import('./lib/telemetry-init')
|
|
1347
|
+
return runTelemetryInit(remaining.filter(Boolean))
|
|
1348
|
+
}
|
|
1349
|
+
console.log('Usage: yarn mercato telemetry init [--dry-run]')
|
|
1350
|
+
console.log(' Wires @open-mercato/telemetry into this app (package.json, .env,')
|
|
1351
|
+
console.log(' instrumentation.ts, next.config.ts, and the API dispatcher).')
|
|
1352
|
+
console.log(' Idempotent and safe to re-run. Use --dry-run to preview changes.')
|
|
1353
|
+
return second && second !== 'help' && second !== '--help' && second !== '-h' ? 1 : 0
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1335
1356
|
if (first === 'module') {
|
|
1336
1357
|
try {
|
|
1337
1358
|
const subcommand = second
|
|
@@ -1654,6 +1675,8 @@ export async function run(argv = process.argv) {
|
|
|
1654
1675
|
effectiveByQueue.get(queue) ??
|
|
1655
1676
|
concurrencyOverride ??
|
|
1656
1677
|
Math.max(...queueWorkers.map((w) => w.concurrency), 1)
|
|
1678
|
+
const maxStalledCount = Math.max(...queueWorkers.map((w) => w.maxStalledCount ?? 1), 1)
|
|
1679
|
+
const lockDuration = Math.max(...queueWorkers.map((w) => w.lockDuration ?? 0), 0) || undefined
|
|
1657
1680
|
|
|
1658
1681
|
console.log(`[worker] Starting "${queue}" with ${queueWorkers.length} handler(s), concurrency: ${concurrency}`)
|
|
1659
1682
|
|
|
@@ -1662,6 +1685,8 @@ export async function run(argv = process.argv) {
|
|
|
1662
1685
|
queueName: queue,
|
|
1663
1686
|
connection: queueRedisUrl ? { url: queueRedisUrl } : undefined,
|
|
1664
1687
|
concurrency,
|
|
1688
|
+
lockDuration,
|
|
1689
|
+
maxStalledCount,
|
|
1665
1690
|
background: true,
|
|
1666
1691
|
handler: createPerJobWorkerHandler(queueWorkers, createRequestContainer),
|
|
1667
1692
|
})
|
|
@@ -1705,6 +1730,8 @@ export async function run(argv = process.argv) {
|
|
|
1705
1730
|
// never check out more pooled connections than the worker pool holds.
|
|
1706
1731
|
const budgetPlan = await resolveWorkerBudgetPlan([{ queue: queueName!, concurrency: requested }])
|
|
1707
1732
|
const concurrency = budgetPlan.entries[0]?.effective ?? requested
|
|
1733
|
+
const maxStalledCount = Math.max(...queueWorkers.map((w) => w.maxStalledCount ?? 1), 1)
|
|
1734
|
+
const lockDuration = Math.max(...queueWorkers.map((w) => w.lockDuration ?? 0), 0) || undefined
|
|
1708
1735
|
|
|
1709
1736
|
console.log(`[worker] Found ${queueWorkers.length} worker(s) for queue "${queueName}"`)
|
|
1710
1737
|
|
|
@@ -1713,6 +1740,8 @@ export async function run(argv = process.argv) {
|
|
|
1713
1740
|
queueName: queueName!,
|
|
1714
1741
|
connection: queueRedisUrl ? { url: queueRedisUrl } : undefined,
|
|
1715
1742
|
concurrency,
|
|
1743
|
+
lockDuration,
|
|
1744
|
+
maxStalledCount,
|
|
1716
1745
|
handler: createPerJobWorkerHandler(queueWorkers, createRequestContainer),
|
|
1717
1746
|
})
|
|
1718
1747
|
} else {
|
|
@@ -1838,7 +1867,7 @@ export async function run(argv = process.argv) {
|
|
|
1838
1867
|
const parsedInterval = intervalArg ? Number.parseInt(intervalArg.split('=')[1] ?? '', 10) : NaN
|
|
1839
1868
|
const intervalMs = Number.isFinite(parsedInterval) && parsedInterval >= 250 ? parsedInterval : 1000
|
|
1840
1869
|
|
|
1841
|
-
const generateWatchRuntime = await createGenerateWatchRuntime()
|
|
1870
|
+
const generateWatchRuntime = await createGenerateWatchRuntime(quiet)
|
|
1842
1871
|
const watcher = startInProcessGenerateWatcher({
|
|
1843
1872
|
pollMs: intervalMs,
|
|
1844
1873
|
skipInitial,
|
|
@@ -1974,6 +2003,10 @@ export async function run(argv = process.argv) {
|
|
|
1974
2003
|
let lastRestartReason: string | null = null
|
|
1975
2004
|
const generateWatcherMode: GenerateWatcherMode = resolveGenerateWatcherMode(process.env)
|
|
1976
2005
|
const envReloader = createDevEnvReloader(appDir, process.env, initialProcessEnvironmentEntries)
|
|
2006
|
+
// The CLI binary registers this primitive manifest before dispatching
|
|
2007
|
+
// `server dev`. Direct `run()` callers keep the existing module-registry
|
|
2008
|
+
// fallback for backward compatibility and focused unit tests.
|
|
2009
|
+
const devSupervisorManifest = getRegisteredDevSupervisorManifest()
|
|
1977
2010
|
|
|
1978
2011
|
function cleanup() {
|
|
1979
2012
|
console.log('[server] Shutting down...')
|
|
@@ -2182,14 +2215,15 @@ export async function run(argv = process.argv) {
|
|
|
2182
2215
|
applyEventsSingleDeliveryGuard({ processEnv: process.env, runtimeEnv, autoSpawnWorkersMode })
|
|
2183
2216
|
const autoSpawnSchedulerMode = resolveAutoSpawnSchedulerMode(process.env)
|
|
2184
2217
|
const queueStrategy = process.env.QUEUE_STRATEGY || 'local'
|
|
2185
|
-
const
|
|
2218
|
+
const schedulerStartStatus = devSupervisorManifest?.schedulerStartStatus
|
|
2219
|
+
?? lookupModuleCommand(getCliModules(), 'scheduler', 'start').status
|
|
2186
2220
|
const embedSchedulerInSharedWorker =
|
|
2187
2221
|
shouldEmbedLocalSchedulerInSharedWorker(process.env)
|
|
2188
2222
|
&& queueStrategy === 'local'
|
|
2189
2223
|
&& autoSpawnWorkersMode === 'lazy'
|
|
2190
2224
|
&& resolveLazySpawnMode(process.env) === 'shared'
|
|
2191
2225
|
&& autoSpawnSchedulerMode !== 'off'
|
|
2192
|
-
&&
|
|
2226
|
+
&& schedulerStartStatus === 'ok'
|
|
2193
2227
|
const nextRuntime = startNextDev(runtimeEnv)
|
|
2194
2228
|
const restartPromise = waitForDevRestart()
|
|
2195
2229
|
const backgroundStartAbort = new AbortController()
|
|
@@ -2223,10 +2257,13 @@ export async function run(argv = process.argv) {
|
|
|
2223
2257
|
}
|
|
2224
2258
|
|
|
2225
2259
|
if (autoSpawnWorkersMode !== 'off') {
|
|
2226
|
-
const discoveredWorkers = getRegisteredCliWorkers()
|
|
2260
|
+
const discoveredWorkers = devSupervisorManifest?.workers ?? getRegisteredCliWorkers()
|
|
2227
2261
|
const discoveredWorkerQueues = [...new Set(discoveredWorkers.map((worker) => worker.queue))]
|
|
2228
2262
|
if (discoveredWorkerQueues.length === 0) {
|
|
2229
|
-
|
|
2263
|
+
const workerRegistryFile = devSupervisorManifest
|
|
2264
|
+
? 'dev-supervisor.generated.json'
|
|
2265
|
+
: 'modules.cli.generated.ts'
|
|
2266
|
+
console.error(`[server] AUTO_SPAWN_WORKERS is enabled, but no queues were discovered from CLI modules. Run \`yarn generate\` and verify \`.mercato/generated/${workerRegistryFile}\` contains worker entries. Continuing without auto-spawned workers.`)
|
|
2230
2267
|
} else if (autoSpawnWorkersMode === 'lazy') {
|
|
2231
2268
|
const lazySpawnMode = resolveLazySpawnMode(process.env)
|
|
2232
2269
|
const lazyModeHint = lazySpawnMode === 'shared'
|
|
@@ -2274,8 +2311,8 @@ export async function run(argv = process.argv) {
|
|
|
2274
2311
|
if (embedSchedulerInSharedWorker && activeLazySupervisor) {
|
|
2275
2312
|
console.log('[server] Local scheduler will run inside the lazy shared worker process.')
|
|
2276
2313
|
} else if (autoSpawnSchedulerMode !== 'off' && queueStrategy === 'local') {
|
|
2277
|
-
if (
|
|
2278
|
-
console.log(`[server] Skipping scheduler auto-start — ${describeMissingModuleCommand(
|
|
2314
|
+
if (schedulerStartStatus !== 'ok') {
|
|
2315
|
+
console.log(`[server] Skipping scheduler auto-start — ${describeMissingModuleCommand({ status: schedulerStartStatus })}`)
|
|
2279
2316
|
} else if (autoSpawnSchedulerMode === 'lazy') {
|
|
2280
2317
|
console.log('[server] Lazy scheduler auto-spawn enabled - scheduler will start when an enabled schedule exists.')
|
|
2281
2318
|
activeLazySchedulerSupervisor = startLazySchedulerSupervisor({
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# Cache Package — Standalone Developer Guide
|
|
2
|
-
|
|
3
|
-
`@open-mercato/cache` provides tenant-scoped caching with tag-based invalidation. MUST NOT use raw Redis, SQLite, or in-memory caching directly.
|
|
4
|
-
|
|
5
|
-
## Strategy Selection
|
|
6
|
-
|
|
7
|
-
| Strategy | When | Config |
|
|
8
|
-
|----------|------|--------|
|
|
9
|
-
| Memory | Development, single-process | Default (no config) |
|
|
10
|
-
| SQLite | Single-server production | `CACHE_STRATEGY=sqlite` |
|
|
11
|
-
| Redis | Multi-server, shared cache | `CACHE_STRATEGY=redis` |
|
|
12
|
-
|
|
13
|
-
## Usage
|
|
14
|
-
|
|
15
|
-
Always resolve via DI:
|
|
16
|
-
|
|
17
|
-
```typescript
|
|
18
|
-
const cacheService = container.resolve('cacheService')
|
|
19
|
-
|
|
20
|
-
// Set with tags for targeted invalidation
|
|
21
|
-
await cacheService.set(`${tenantId}:my_module:stats`, value, {
|
|
22
|
-
tags: [`tenant:${tenantId}`, 'my_module'],
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
// Get
|
|
26
|
-
const cached = await cacheService.get(`${tenantId}:my_module:stats`)
|
|
27
|
-
|
|
28
|
-
// Invalidate by tag (clears all entries with that tag)
|
|
29
|
-
await cacheService.invalidateTag('my_module')
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## MUST Rules
|
|
33
|
-
|
|
34
|
-
1. **MUST resolve via DI** — `container.resolve('cacheService')`, never instantiate directly
|
|
35
|
-
2. **MUST scope to tenant** — include `tenantId` in cache keys
|
|
36
|
-
3. **MUST use tag-based invalidation** for CRUD side effects
|
|
37
|
-
4. **MUST NOT cache sensitive data** without encryption
|
|
38
|
-
|
|
39
|
-
## Adding Cache to a Module
|
|
40
|
-
|
|
41
|
-
1. Resolve `cacheService` from DI in your service or route handler
|
|
42
|
-
2. Define cache keys: `${tenantId}:${module}:${identifier}`
|
|
43
|
-
3. Tag entries: `{ tags: ['tenant:123', 'my_module'] }`
|
|
44
|
-
4. Add cache invalidation to CRUD side effects (`emitCrudSideEffects` with `cacheAliases`)
|
|
45
|
-
5. Test with `CACHE_STRATEGY=memory` (default in dev)
|
|
@@ -1,453 +0,0 @@
|
|
|
1
|
-
# Core Package — Standalone Developer Guide
|
|
2
|
-
|
|
3
|
-
`@open-mercato/core` contains all built-in business modules. This guide covers module development patterns for standalone apps that build on top of these modules.
|
|
4
|
-
|
|
5
|
-
## Module anatomy, auto-discovery & convention files
|
|
6
|
-
|
|
7
|
-
> Moved to the conceptual guide. See **`.ai/guides/module-system.md` → Module anatomy** for the auto-discovery path table and the convention-file reference — they are framework-wide, not core-specific. The sections below cover `@open-mercato/core`-specific development patterns.
|
|
8
|
-
|
|
9
|
-
## API Routes
|
|
10
|
-
|
|
11
|
-
Every API route file MUST export an `openApi` object:
|
|
12
|
-
|
|
13
|
-
```typescript
|
|
14
|
-
import { createCrudOpenApiFactory } from '@open-mercato/shared/lib/openapi/crud'
|
|
15
|
-
const buildOpenApi = createCrudOpenApiFactory({ defaultTag: 'MyModule' })
|
|
16
|
-
|
|
17
|
-
export const openApi = buildOpenApi({
|
|
18
|
-
resourceName: 'Item',
|
|
19
|
-
querySchema: listQuerySchema,
|
|
20
|
-
listResponseSchema: createPagedListResponseSchema(itemSchema),
|
|
21
|
-
create: { schema: createSchema, description: 'Create item' },
|
|
22
|
-
update: { schema: updateSchema, responseSchema: okSchema },
|
|
23
|
-
del: { schema: deleteSchema, responseSchema: okSchema },
|
|
24
|
-
})
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### CRUD Routes with makeCrudRoute
|
|
28
|
-
|
|
29
|
-
Always set `indexer: { entityType }` for query index coverage:
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
makeCrudRoute({
|
|
33
|
-
entity: MyEntity,
|
|
34
|
-
indexer: { entityType: 'my_module:my_entity' },
|
|
35
|
-
enrichers: { entityId: 'my_module.my_entity' }, // opt-in to enrichers
|
|
36
|
-
// ...
|
|
37
|
-
})
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
### Custom Write Routes
|
|
41
|
-
|
|
42
|
-
For non-CRUD write routes (`POST`/`PUT`/`PATCH`/`DELETE`), MUST wire mutation guards:
|
|
43
|
-
- Map the route to the closest registry operation (`create`, `update`, or `delete`; state-changing action endpoints usually use `update`)
|
|
44
|
-
- Collect registered guards with `getAllMutationGuardInstances()` and append `bridgeLegacyGuard(container)` when present
|
|
45
|
-
- Call `runMutationGuards(...)` from `@open-mercato/shared/lib/crud/mutation-guard-registry` before mutation, passing the caller's granted features as `{ userFeatures }`
|
|
46
|
-
- Return guard rejection bodies/statuses, merge `modifiedPayload` into validated input when present, and run returned `afterSuccessCallbacks` after successful mutation, catching/logging callback failures so committed writes still return successfully
|
|
47
|
-
|
|
48
|
-
## Module Setup (`setup.ts`)
|
|
49
|
-
|
|
50
|
-
Required for tenant initialization:
|
|
51
|
-
|
|
52
|
-
```typescript
|
|
53
|
-
import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'
|
|
54
|
-
|
|
55
|
-
export const setup: ModuleSetupConfig = {
|
|
56
|
-
defaultRoleFeatures: {
|
|
57
|
-
superadmin: ['my_module.admin_only'],
|
|
58
|
-
admin: ['my_module.*'],
|
|
59
|
-
employee: ['my_module.view'],
|
|
60
|
-
},
|
|
61
|
-
async onTenantCreated({ em, tenantId, organizationId }) { /* settings, sequences */ },
|
|
62
|
-
async seedDefaults({ em, tenantId, container }) { /* dictionaries, statuses */ },
|
|
63
|
-
async seedExamples({ em, tenantId, container }) { /* demo data */ },
|
|
64
|
-
}
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
## Events
|
|
68
|
-
|
|
69
|
-
Declare events in the emitting module's `events.ts`:
|
|
70
|
-
|
|
71
|
-
```typescript
|
|
72
|
-
import { createModuleEvents } from '@open-mercato/shared/modules/events'
|
|
73
|
-
const events = [
|
|
74
|
-
{ id: 'my_mod.item.created', label: 'Item Created', entity: 'item', category: 'crud' },
|
|
75
|
-
{ id: 'my_mod.item.updated', label: 'Item Updated', entity: 'item', category: 'crud' },
|
|
76
|
-
{ id: 'my_mod.item.deleted', label: 'Item Deleted', entity: 'item', category: 'crud' },
|
|
77
|
-
] as const
|
|
78
|
-
export const eventsConfig = createModuleEvents({ moduleId: 'my_mod', events })
|
|
79
|
-
export const emitMyModEvent = eventsConfig.emit
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
MUST use `as const` for type safety. Run `yarn generate` after adding.
|
|
83
|
-
|
|
84
|
-
### Subscribers
|
|
85
|
-
|
|
86
|
-
```typescript
|
|
87
|
-
// subscribers/item-created-notify.ts
|
|
88
|
-
export const metadata = { event: 'my_mod.item.created', persistent: true, id: 'item-created-notify' }
|
|
89
|
-
export default async function handler(payload, ctx) { /* one side effect per subscriber */ }
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## Widget Injection
|
|
93
|
-
|
|
94
|
-
The preferred way to extend other modules' UI without direct coupling.
|
|
95
|
-
|
|
96
|
-
### Structure
|
|
97
|
-
- Widgets: `widgets/injection/<WidgetName>/widget.tsx` (or `widget.ts` for headless)
|
|
98
|
-
- Mapping: `widgets/injection-table.ts`
|
|
99
|
-
|
|
100
|
-
### Spot IDs
|
|
101
|
-
- `crud-form:<entityId>` — inject into forms
|
|
102
|
-
- `crud-form:<entityId>:fields` — inject form fields
|
|
103
|
-
- `data-table:<tableId>:columns|row-actions|bulk-actions|filters` — inject into tables
|
|
104
|
-
- `menu:sidebar:main|settings|profile` — sidebar menu items
|
|
105
|
-
- `menu:topbar:profile-dropdown|actions` — topbar items
|
|
106
|
-
|
|
107
|
-
### Menu Injection (Headless)
|
|
108
|
-
```typescript
|
|
109
|
-
// widgets/injection/MyMenuItem/widget.ts
|
|
110
|
-
export const menuItems = [
|
|
111
|
-
{ id: 'my-mod-dashboard', labelKey: 'my_mod.menu.dashboard', icon: 'lucide:layout-dashboard',
|
|
112
|
-
href: '/backend/my-module', placement: { position: InjectionPosition.After, relativeTo: 'customers' } }
|
|
113
|
-
]
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
Map in `injection-table.ts`:
|
|
117
|
-
```typescript
|
|
118
|
-
export default [{ widgetId: 'MyMenuItem', spots: ['menu:sidebar:main'] }]
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
## Response Enrichers
|
|
122
|
-
|
|
123
|
-
Add computed fields to another module's CRUD responses:
|
|
124
|
-
|
|
125
|
-
```typescript
|
|
126
|
-
// data/enrichers.ts
|
|
127
|
-
export const enrichers: ResponseEnricher[] = [{
|
|
128
|
-
id: 'my_mod.customer-stats',
|
|
129
|
-
targetEntity: 'customers.person',
|
|
130
|
-
features: ['my_mod.view'],
|
|
131
|
-
priority: 10,
|
|
132
|
-
timeout: 2000,
|
|
133
|
-
fallback: { _my_mod: { count: 0 } },
|
|
134
|
-
async enrichMany(records, ctx) {
|
|
135
|
-
return records.map(r => ({ ...r, _my_mod: { count: 42 } }))
|
|
136
|
-
},
|
|
137
|
-
}]
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
MUST implement `enrichMany()` for batch endpoints. MUST namespace with `_moduleName` prefix.
|
|
141
|
-
|
|
142
|
-
## API Interceptors
|
|
143
|
-
|
|
144
|
-
Hook into any route's before/after lifecycle:
|
|
145
|
-
|
|
146
|
-
```typescript
|
|
147
|
-
// api/interceptors.ts
|
|
148
|
-
export const interceptors: ApiInterceptor[] = [{
|
|
149
|
-
id: 'my_mod.narrow-customers',
|
|
150
|
-
targetRoute: '/api/customers/people',
|
|
151
|
-
methods: ['GET'],
|
|
152
|
-
async before(ctx) { /* rewrite query.ids to narrow results */ },
|
|
153
|
-
async after(ctx, response) { /* transform response */ },
|
|
154
|
-
}]
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
## Access Control (RBAC)
|
|
158
|
-
|
|
159
|
-
Declare features in `acl.ts`, guard with metadata:
|
|
160
|
-
|
|
161
|
-
```typescript
|
|
162
|
-
// acl.ts
|
|
163
|
-
export const features = ['my_module.view', 'my_module.create', 'my_module.edit', 'my_module.delete']
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
Always add matching `defaultRoleFeatures` in `setup.ts`.
|
|
167
|
-
|
|
168
|
-
Use declarative guards in page metadata: `requireAuth`, `requireRoles`, `requireFeatures`.
|
|
169
|
-
|
|
170
|
-
## Custom Fields & Entities
|
|
171
|
-
|
|
172
|
-
Declare in `ce.ts` using DSL helpers:
|
|
173
|
-
|
|
174
|
-
```typescript
|
|
175
|
-
import { defineFields, cf } from '@open-mercato/shared/modules/dsl'
|
|
176
|
-
export const entities = [{
|
|
177
|
-
entityId: 'my_module:my_entity',
|
|
178
|
-
fields: defineFields({ fields: [cf.text('notes'), cf.number('priority')] }),
|
|
179
|
-
}]
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
## Entity Extensions (Cross-Module Links)
|
|
183
|
-
|
|
184
|
-
Extend another module's data without mutating their entities:
|
|
185
|
-
|
|
186
|
-
```typescript
|
|
187
|
-
// data/extensions.ts
|
|
188
|
-
import { defineLink, entityId, linkable } from '@open-mercato/shared/modules/dsl'
|
|
189
|
-
export const extensions = [
|
|
190
|
-
defineLink({ source: entityId('my_module:my_entity'), target: linkable('customers:person') })
|
|
191
|
-
]
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
## Component Replacement
|
|
195
|
-
|
|
196
|
-
Override or wrap existing UI components:
|
|
197
|
-
|
|
198
|
-
```typescript
|
|
199
|
-
// widgets/components.ts
|
|
200
|
-
export const componentOverrides = [{
|
|
201
|
-
handle: 'page:customers:detail',
|
|
202
|
-
mode: 'wrapper', // or 'replace', 'props'
|
|
203
|
-
component: MyCustomerDetailWrapper,
|
|
204
|
-
}]
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
Prefer `wrapper`/`props` modes over full `replace`.
|
|
208
|
-
|
|
209
|
-
## Command Pattern (Write Operations)
|
|
210
|
-
|
|
211
|
-
Implement writes via commands for undo/redo support:
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
import { registerCommand } from '@open-mercato/shared/lib/commands'
|
|
215
|
-
// Reference: @open-mercato/core customers/commands/people.ts
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
Include `indexer: { entityType, cacheAliases }` in `emitCrudSideEffects` for query index refresh.
|
|
219
|
-
|
|
220
|
-
## Customer Accounts
|
|
221
|
-
|
|
222
|
-
The `customer_accounts` module provides customer-facing authentication, sessions, RBAC, and account management. App modules build on top of it to add portal features.
|
|
223
|
-
|
|
224
|
-
### Auth APIs
|
|
225
|
-
|
|
226
|
-
| Endpoint | Purpose |
|
|
227
|
-
|----------|---------|
|
|
228
|
-
| `POST /api/customer_accounts/login` | Email + password login, returns JWT + session cookies |
|
|
229
|
-
| `POST /api/customer_accounts/signup` | Self-registration |
|
|
230
|
-
| `POST /api/customer_accounts/magic-link/request` | Request passwordless login link |
|
|
231
|
-
| `POST /api/customer_accounts/magic-link/verify` | Verify magic link token |
|
|
232
|
-
| `POST /api/customer_accounts/password/reset-request` | Request password reset email |
|
|
233
|
-
| `POST /api/customer_accounts/password/reset-confirm` | Confirm password reset with token |
|
|
234
|
-
| `POST /api/customer_accounts/email/verify` | Verify email address |
|
|
235
|
-
| `POST /api/customer_accounts/invitations/accept` | Accept an invitation token |
|
|
236
|
-
| `POST /api/customer_accounts/portal/logout` | Clear customer session |
|
|
237
|
-
| `POST /api/customer_accounts/portal/sessions-refresh` | Refresh JWT from session token |
|
|
238
|
-
| `POST /api/customer_accounts/portal/password-change` | Change password (authenticated) |
|
|
239
|
-
|
|
240
|
-
### Server-Side Auth Check
|
|
241
|
-
|
|
242
|
-
Use `getCustomerAuthFromCookies` in server components and catch-all routes:
|
|
243
|
-
|
|
244
|
-
```typescript
|
|
245
|
-
import { getCustomerAuthFromCookies } from '@open-mercato/core/modules/customer_accounts/lib/customerAuthServer'
|
|
246
|
-
|
|
247
|
-
const auth = await getCustomerAuthFromCookies()
|
|
248
|
-
if (!auth) redirect(`/${orgSlug}/portal/login`)
|
|
249
|
-
// auth.sub, auth.email, auth.displayName, auth.resolvedFeatures, auth.customerEntityId
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
### Client-Side Auth
|
|
253
|
-
|
|
254
|
-
Use `useCustomerAuth` in portal client components:
|
|
255
|
-
|
|
256
|
-
```typescript
|
|
257
|
-
import { useCustomerAuth } from '@open-mercato/ui/portal/hooks/useCustomerAuth'
|
|
258
|
-
|
|
259
|
-
const { user, roles, resolvedFeatures, isPortalAdmin, loading, logout } = useCustomerAuth(orgSlug)
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
### Customer RBAC
|
|
263
|
-
|
|
264
|
-
Declare `defaultCustomerRoleFeatures` in your module's `setup.ts` to auto-merge features into customer roles during tenant setup:
|
|
265
|
-
|
|
266
|
-
```typescript
|
|
267
|
-
import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'
|
|
268
|
-
|
|
269
|
-
export const setup: ModuleSetupConfig = {
|
|
270
|
-
defaultCustomerRoleFeatures: {
|
|
271
|
-
buyer: ['portal.my_module.view', 'portal.my_module.create'],
|
|
272
|
-
viewer: ['portal.my_module.view'],
|
|
273
|
-
},
|
|
274
|
-
}
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
Built-in customer roles: `portal_admin` (full access), `buyer` (order/quote/catalog), `viewer` (read-only). Features merge additively on `seedDefaults`.
|
|
278
|
-
|
|
279
|
-
### Portal Page Auth Metadata
|
|
280
|
-
|
|
281
|
-
Guard portal pages declaratively via page metadata:
|
|
282
|
-
|
|
283
|
-
```typescript
|
|
284
|
-
// frontend/[orgSlug]/portal/my-feature/page.meta.ts
|
|
285
|
-
export const metadata = {
|
|
286
|
-
requireCustomerAuth: true,
|
|
287
|
-
requireCustomerFeatures: ['portal.my_module.view'],
|
|
288
|
-
navHidden: true,
|
|
289
|
-
}
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
### Events
|
|
293
|
-
|
|
294
|
-
Subscribe to customer account lifecycle events:
|
|
295
|
-
|
|
296
|
-
| Event ID | Category | Broadcast |
|
|
297
|
-
|----------|----------|-----------|
|
|
298
|
-
| `customer_accounts.user.created` | crud | `clientBroadcast` |
|
|
299
|
-
| `customer_accounts.user.updated` | crud | |
|
|
300
|
-
| `customer_accounts.user.deleted` | crud | |
|
|
301
|
-
| `customer_accounts.user.locked` | lifecycle | |
|
|
302
|
-
| `customer_accounts.user.unlocked` | lifecycle | |
|
|
303
|
-
| `customer_accounts.login.success` | lifecycle | |
|
|
304
|
-
| `customer_accounts.login.failed` | lifecycle | |
|
|
305
|
-
| `customer_accounts.email.verified` | lifecycle | |
|
|
306
|
-
| `customer_accounts.password.reset` | lifecycle | |
|
|
307
|
-
| `customer_accounts.invitation.accepted` | lifecycle | `clientBroadcast` |
|
|
308
|
-
| `customer_accounts.role.created` | crud | |
|
|
309
|
-
| `customer_accounts.role.updated` | crud | |
|
|
310
|
-
| `customer_accounts.role.deleted` | crud | |
|
|
311
|
-
|
|
312
|
-
```typescript
|
|
313
|
-
// subscribers/on-customer-signup.ts
|
|
314
|
-
export const metadata = { event: 'customer_accounts.user.created', persistent: true, id: 'my-mod-on-customer-signup' }
|
|
315
|
-
export default async function handler(payload, ctx) { /* welcome email, CRM sync, etc. */ }
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
### Extending Customer Data
|
|
319
|
-
|
|
320
|
-
Link your module's entities to `CustomerUser` via `data/extensions.ts`:
|
|
321
|
-
|
|
322
|
-
```typescript
|
|
323
|
-
import { defineLink, entityId, linkable } from '@open-mercato/shared/modules/dsl'
|
|
324
|
-
export const extensions = [
|
|
325
|
-
defineLink({ source: entityId('my_module:preference'), target: linkable('customer_accounts:user') }),
|
|
326
|
-
]
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
Key entities: `CustomerUser` (customer_users), `CustomerRole` (customer_roles), `CustomerRoleAcl`, `CustomerUserRole`, `CustomerUserSession`, `CustomerUserInvitation`.
|
|
330
|
-
|
|
331
|
-
### Admin RBAC Features
|
|
332
|
-
|
|
333
|
-
Staff-side features for managing customer accounts:
|
|
334
|
-
|
|
335
|
-
| Feature | Purpose |
|
|
336
|
-
|---------|---------|
|
|
337
|
-
| `customer_accounts.view` | View customer accounts |
|
|
338
|
-
| `customer_accounts.manage` | Manage customer accounts |
|
|
339
|
-
| `customer_accounts.roles.manage` | Manage customer roles |
|
|
340
|
-
| `customer_accounts.invite` | Invite customer users |
|
|
341
|
-
|
|
342
|
-
## Portal Extension
|
|
343
|
-
|
|
344
|
-
Build customer-facing portal pages that integrate with the shared portal shell, navigation, and event bridge.
|
|
345
|
-
|
|
346
|
-
### Portal Feature Toggle
|
|
347
|
-
|
|
348
|
-
The portal is gated by the `portal_enabled` feature toggle (seeded by the `portal` module). When disabled, all portal routes show a "Portal not available" message.
|
|
349
|
-
|
|
350
|
-
### Portal Page Structure
|
|
351
|
-
|
|
352
|
-
Portal pages live at `frontend/[orgSlug]/portal/<feature>/page.tsx` and use the portal shell:
|
|
353
|
-
|
|
354
|
-
```typescript
|
|
355
|
-
'use client'
|
|
356
|
-
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
357
|
-
import { usePortalContext } from '@open-mercato/ui/portal/PortalContext'
|
|
358
|
-
import { PortalPageHeader } from '@open-mercato/ui/portal/components/PortalPageHeader'
|
|
359
|
-
import { PortalCard, PortalCardHeader } from '@open-mercato/ui/portal/components/PortalCard'
|
|
360
|
-
|
|
361
|
-
export default function MyPortalPage({ params }: { params: { orgSlug: string } }) {
|
|
362
|
-
const t = useT()
|
|
363
|
-
const { auth } = usePortalContext()
|
|
364
|
-
const { user, loading } = auth
|
|
365
|
-
|
|
366
|
-
if (loading) return <Spinner />
|
|
367
|
-
if (!user) return null
|
|
368
|
-
|
|
369
|
-
return (
|
|
370
|
-
<div className="flex flex-col gap-8">
|
|
371
|
-
<PortalPageHeader label={t('my_mod.portal.label')} title={t('my_mod.portal.title')} />
|
|
372
|
-
<PortalCard>
|
|
373
|
-
<PortalCardHeader label="Section" title="Content" />
|
|
374
|
-
{/* page content */}
|
|
375
|
-
</PortalCard>
|
|
376
|
-
</div>
|
|
377
|
-
)
|
|
378
|
-
}
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
### Portal Menu Injection
|
|
382
|
-
|
|
383
|
-
Inject navigation items into portal chrome surfaces:
|
|
384
|
-
|
|
385
|
-
| Spot ID | Purpose |
|
|
386
|
-
|---------|---------|
|
|
387
|
-
| `menu:portal:sidebar:main` | Main portal navigation |
|
|
388
|
-
| `menu:portal:sidebar:account` | Account/settings section |
|
|
389
|
-
| `menu:portal:header:actions` | Header action buttons |
|
|
390
|
-
| `menu:portal:user-dropdown` | User dropdown items |
|
|
391
|
-
|
|
392
|
-
```typescript
|
|
393
|
-
// widgets/injection/PortalNavItem/widget.ts
|
|
394
|
-
import { InjectionPosition } from '@open-mercato/shared/modules/widgets/injection-position'
|
|
395
|
-
|
|
396
|
-
export const menuItems = [{
|
|
397
|
-
id: 'my-mod-portal-nav',
|
|
398
|
-
labelKey: 'my_mod.portal.menu.label',
|
|
399
|
-
icon: 'lucide:package',
|
|
400
|
-
href: '/portal/my-feature',
|
|
401
|
-
placement: { position: InjectionPosition.After, relativeTo: 'portal-dashboard' },
|
|
402
|
-
}]
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
Map in `widgets/injection-table.ts`:
|
|
406
|
-
```typescript
|
|
407
|
-
export default { 'menu:portal:sidebar:main': { widgetId: 'my_mod.injection.portal-nav', priority: 10 } }
|
|
408
|
-
```
|
|
409
|
-
|
|
410
|
-
### Portal Widget Injection Spots
|
|
411
|
-
|
|
412
|
-
Inject content into portal dashboard and pages:
|
|
413
|
-
|
|
414
|
-
| Spot ID | Purpose |
|
|
415
|
-
|---------|---------|
|
|
416
|
-
| `portal:dashboard:sections` | Dashboard section cards |
|
|
417
|
-
| `portal:dashboard:profile` | Dashboard profile area |
|
|
418
|
-
| `portal:dashboard:sidebar` | Dashboard sidebar |
|
|
419
|
-
| `portal:<pageId>:before` | Before any portal page content |
|
|
420
|
-
| `portal:<pageId>:after` | After any portal page content |
|
|
421
|
-
|
|
422
|
-
### Portal Event Bridge
|
|
423
|
-
|
|
424
|
-
Events with `portalBroadcast: true` stream to authenticated portal users via SSE at `/api/customer_accounts/portal/events/stream`.
|
|
425
|
-
|
|
426
|
-
Declare portal-broadcast events:
|
|
427
|
-
```typescript
|
|
428
|
-
// events.ts
|
|
429
|
-
const events = [
|
|
430
|
-
{ id: 'my_mod.item.status_changed', label: 'Item Status Changed', portalBroadcast: true },
|
|
431
|
-
] as const
|
|
432
|
-
```
|
|
433
|
-
|
|
434
|
-
Listen in portal components:
|
|
435
|
-
```typescript
|
|
436
|
-
import { usePortalAppEvent } from '@open-mercato/ui/portal/hooks/usePortalAppEvent'
|
|
437
|
-
|
|
438
|
-
usePortalAppEvent('my_mod.item.status_changed', (event) => { refetch() })
|
|
439
|
-
```
|
|
440
|
-
|
|
441
|
-
Enable the bridge in PortalShell with `enableEventBridge` prop.
|
|
442
|
-
|
|
443
|
-
### Portal Component Replacement
|
|
444
|
-
|
|
445
|
-
Override portal shell sections via component replacement handles:
|
|
446
|
-
|
|
447
|
-
| Handle | Purpose |
|
|
448
|
-
|--------|---------|
|
|
449
|
-
| `page:portal:layout` | Entire portal shell |
|
|
450
|
-
| `section:portal:header` | Header bar |
|
|
451
|
-
| `section:portal:footer` | Footer |
|
|
452
|
-
| `section:portal:sidebar` | Navigation sidebar |
|
|
453
|
-
| `section:portal:user-menu` | User dropdown |
|