@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
package/src/mercato.ts
CHANGED
|
@@ -867,7 +867,10 @@ async function buildAllModules(): Promise<Module[]> {
|
|
|
867
867
|
export async function run(argv = process.argv) {
|
|
868
868
|
const [, , ...parts] = argv
|
|
869
869
|
const [first, second, ...remaining] = parts
|
|
870
|
-
await ensureEnvLoaded({
|
|
870
|
+
await ensureEnvLoaded({
|
|
871
|
+
createIfMissing: first !== 'deploy' && first !== 'telemetry',
|
|
872
|
+
quiet: first === 'deploy' || first === 'telemetry',
|
|
873
|
+
})
|
|
871
874
|
|
|
872
875
|
// Handle init command directly
|
|
873
876
|
if (first === 'init') {
|
|
@@ -1332,6 +1335,20 @@ export async function run(argv = process.argv) {
|
|
|
1332
1335
|
return exitCode
|
|
1333
1336
|
}
|
|
1334
1337
|
|
|
1338
|
+
// Handle telemetry command (bootstrap-free) — wires @open-mercato/telemetry
|
|
1339
|
+
// into an app scaffolded before telemetry existed.
|
|
1340
|
+
if (first === 'telemetry') {
|
|
1341
|
+
if (second === 'init') {
|
|
1342
|
+
const { runTelemetryInit } = await import('./lib/telemetry-init')
|
|
1343
|
+
return runTelemetryInit(remaining.filter(Boolean))
|
|
1344
|
+
}
|
|
1345
|
+
console.log('Usage: yarn mercato telemetry init [--dry-run]')
|
|
1346
|
+
console.log(' Wires @open-mercato/telemetry into this app (package.json, .env,')
|
|
1347
|
+
console.log(' instrumentation.ts, next.config.ts, and the API dispatcher).')
|
|
1348
|
+
console.log(' Idempotent and safe to re-run. Use --dry-run to preview changes.')
|
|
1349
|
+
return second && second !== 'help' && second !== '--help' && second !== '-h' ? 1 : 0
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1335
1352
|
if (first === 'module') {
|
|
1336
1353
|
try {
|
|
1337
1354
|
const subcommand = second
|
|
@@ -1654,6 +1671,8 @@ export async function run(argv = process.argv) {
|
|
|
1654
1671
|
effectiveByQueue.get(queue) ??
|
|
1655
1672
|
concurrencyOverride ??
|
|
1656
1673
|
Math.max(...queueWorkers.map((w) => w.concurrency), 1)
|
|
1674
|
+
const maxStalledCount = Math.max(...queueWorkers.map((w) => w.maxStalledCount ?? 1), 1)
|
|
1675
|
+
const lockDuration = Math.max(...queueWorkers.map((w) => w.lockDuration ?? 0), 0) || undefined
|
|
1657
1676
|
|
|
1658
1677
|
console.log(`[worker] Starting "${queue}" with ${queueWorkers.length} handler(s), concurrency: ${concurrency}`)
|
|
1659
1678
|
|
|
@@ -1662,6 +1681,8 @@ export async function run(argv = process.argv) {
|
|
|
1662
1681
|
queueName: queue,
|
|
1663
1682
|
connection: queueRedisUrl ? { url: queueRedisUrl } : undefined,
|
|
1664
1683
|
concurrency,
|
|
1684
|
+
lockDuration,
|
|
1685
|
+
maxStalledCount,
|
|
1665
1686
|
background: true,
|
|
1666
1687
|
handler: createPerJobWorkerHandler(queueWorkers, createRequestContainer),
|
|
1667
1688
|
})
|
|
@@ -1705,6 +1726,8 @@ export async function run(argv = process.argv) {
|
|
|
1705
1726
|
// never check out more pooled connections than the worker pool holds.
|
|
1706
1727
|
const budgetPlan = await resolveWorkerBudgetPlan([{ queue: queueName!, concurrency: requested }])
|
|
1707
1728
|
const concurrency = budgetPlan.entries[0]?.effective ?? requested
|
|
1729
|
+
const maxStalledCount = Math.max(...queueWorkers.map((w) => w.maxStalledCount ?? 1), 1)
|
|
1730
|
+
const lockDuration = Math.max(...queueWorkers.map((w) => w.lockDuration ?? 0), 0) || undefined
|
|
1708
1731
|
|
|
1709
1732
|
console.log(`[worker] Found ${queueWorkers.length} worker(s) for queue "${queueName}"`)
|
|
1710
1733
|
|
|
@@ -1713,6 +1736,8 @@ export async function run(argv = process.argv) {
|
|
|
1713
1736
|
queueName: queueName!,
|
|
1714
1737
|
connection: queueRedisUrl ? { url: queueRedisUrl } : undefined,
|
|
1715
1738
|
concurrency,
|
|
1739
|
+
lockDuration,
|
|
1740
|
+
maxStalledCount,
|
|
1716
1741
|
handler: createPerJobWorkerHandler(queueWorkers, createRequestContainer),
|
|
1717
1742
|
})
|
|
1718
1743
|
} else {
|
|
@@ -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 |
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
# Events Package — Standalone Developer Guide
|
|
2
|
-
|
|
3
|
-
`@open-mercato/events` provides event-driven communication between modules. Use events for side effects — never direct module-to-module function calls.
|
|
4
|
-
|
|
5
|
-
## Declaring Events
|
|
6
|
-
|
|
7
|
-
Every module that emits events MUST declare them in `events.ts`:
|
|
8
|
-
|
|
9
|
-
```typescript
|
|
10
|
-
import { createModuleEvents } from '@open-mercato/shared/modules/events'
|
|
11
|
-
|
|
12
|
-
const events = [
|
|
13
|
-
{ id: 'my_mod.item.created', label: 'Item Created', entity: 'item', category: 'crud' },
|
|
14
|
-
{ id: 'my_mod.item.updated', label: 'Item Updated', entity: 'item', category: 'crud' },
|
|
15
|
-
{ id: 'my_mod.item.deleted', label: 'Item Deleted', entity: 'item', category: 'crud' },
|
|
16
|
-
] as const
|
|
17
|
-
|
|
18
|
-
export const eventsConfig = createModuleEvents({ moduleId: 'my_mod', events })
|
|
19
|
-
export const emitMyModEvent = eventsConfig.emit
|
|
20
|
-
export default eventsConfig
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
MUST use `as const` — provides compile-time safety. Run `yarn generate` after adding.
|
|
24
|
-
|
|
25
|
-
### Event ID Convention
|
|
26
|
-
|
|
27
|
-
Format: `module.entity.action` (singular entity, past tense action, dots as separators).
|
|
28
|
-
|
|
29
|
-
## Adding Subscribers
|
|
30
|
-
|
|
31
|
-
Create subscriber files in `src/modules/<module>/subscribers/`:
|
|
32
|
-
|
|
33
|
-
```typescript
|
|
34
|
-
// subscribers/item-created-notify.ts
|
|
35
|
-
export const metadata = {
|
|
36
|
-
event: 'my_mod.item.created',
|
|
37
|
-
persistent: true, // survives restarts, retried on failure
|
|
38
|
-
id: 'item-created-notify',
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export default async function handler(payload, ctx) {
|
|
42
|
-
// One side effect per subscriber
|
|
43
|
-
}
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
Run `yarn generate` after adding.
|
|
47
|
-
|
|
48
|
-
## Subscription Types
|
|
49
|
-
|
|
50
|
-
| Type | Use for | Behavior |
|
|
51
|
-
|------|---------|----------|
|
|
52
|
-
| Ephemeral (`persistent: false`) | Real-time UI updates, cache invalidation | In-memory, no retry, lost on restart |
|
|
53
|
-
| Persistent (`persistent: true`) | Notifications, indexing, audit logging | Queue-backed, retried on failure |
|
|
54
|
-
|
|
55
|
-
Persistent subscribers MUST be idempotent — they may be retried.
|
|
56
|
-
|
|
57
|
-
## DOM Event Bridge (Browser Events via SSE)
|
|
58
|
-
|
|
59
|
-
Stream server-side events to the browser for real-time UI updates.
|
|
60
|
-
|
|
61
|
-
### Enable on Events
|
|
62
|
-
|
|
63
|
-
Add `clientBroadcast: true` to event declarations:
|
|
64
|
-
|
|
65
|
-
```typescript
|
|
66
|
-
const events = [
|
|
67
|
-
{ id: 'my_mod.item.created', label: 'Created', category: 'crud', clientBroadcast: true },
|
|
68
|
-
] as const
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
### Consume in Components
|
|
72
|
-
|
|
73
|
-
```typescript
|
|
74
|
-
import { useAppEvent } from '@open-mercato/ui/backend/injection/useAppEvent'
|
|
75
|
-
|
|
76
|
-
// Wildcard: all events from a module
|
|
77
|
-
useAppEvent('my_mod.*', (event) => {
|
|
78
|
-
// event.id, event.payload — refresh data, show notification
|
|
79
|
-
}, [])
|
|
80
|
-
|
|
81
|
-
// Exact match
|
|
82
|
-
useAppEvent('my_mod.item.created', (event) => {
|
|
83
|
-
reloadItems()
|
|
84
|
-
}, [reloadItems])
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Track Long-Running Operations
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
90
|
-
import { useOperationProgress } from '@open-mercato/ui/backend/injection/useOperationProgress'
|
|
91
|
-
|
|
92
|
-
const progress = useOperationProgress('my_mod.import.*')
|
|
93
|
-
// progress.status: 'idle' | 'running' | 'completed' | 'failed'
|
|
94
|
-
// progress.progress: 0-100
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
### SSE Audience Filtering
|
|
98
|
-
|
|
99
|
-
Events are server-filtered before SSE delivery by:
|
|
100
|
-
- `tenantId` (must match)
|
|
101
|
-
- `organizationId` / `organizationIds` (must match selected org)
|
|
102
|
-
- `recipientUserId` / `recipientUserIds` (must include user)
|
|
103
|
-
- `recipientRoleId` / `recipientRoleIds` (must intersect roles)
|
|
104
|
-
|
|
105
|
-
Missing `tenantId` in payload = no delivery.
|
|
106
|
-
|
|
107
|
-
## Queue Integration
|
|
108
|
-
|
|
109
|
-
| Queue strategy | Ephemeral | Persistent |
|
|
110
|
-
|----------------|-----------|------------|
|
|
111
|
-
| `local` (dev) | In-process | `.mercato/queue/` filesystem |
|
|
112
|
-
| `async` (prod) | In-process | BullMQ (Redis-backed) |
|
|
113
|
-
|
|
114
|
-
For production, start the event worker:
|
|
115
|
-
```bash
|
|
116
|
-
yarn mercato events worker event-processing --concurrency=5
|
|
117
|
-
```
|