@open-mercato/cli 0.6.5 → 0.6.6-canary.315.1.g6673635216
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 +57 -2
- package/build.mjs +80 -2
- package/dist/agentic/guides/core.auth.md +2 -100
- package/dist/agentic/guides/core.catalog.md +2 -78
- package/dist/agentic/guides/core.currencies.md +2 -42
- package/dist/agentic/guides/core.customer_accounts.md +2 -128
- package/dist/agentic/guides/core.customers.md +2 -137
- package/dist/agentic/guides/core.data_sync.md +2 -106
- package/dist/agentic/guides/core.integrations.md +2 -112
- package/dist/agentic/guides/core.md +7 -40
- package/dist/agentic/guides/core.sales.md +2 -103
- package/dist/agentic/guides/core.workflows.md +2 -151
- package/dist/agentic/guides/module-facts.json +5100 -0
- package/dist/agentic/guides/module-system.md +130 -0
- package/dist/agentic/guides/modules/ai_assistant.md +57 -0
- package/dist/agentic/guides/modules/api_docs.md +39 -0
- package/dist/agentic/guides/modules/api_keys.md +41 -0
- package/dist/agentic/guides/modules/attachments.md +46 -0
- package/dist/agentic/guides/modules/audit_logs.md +42 -0
- package/dist/agentic/guides/modules/auth.md +64 -0
- package/dist/agentic/guides/modules/business_rules.md +44 -0
- package/dist/agentic/guides/modules/catalog.md +70 -0
- package/dist/agentic/guides/modules/channel_gmail.md +39 -0
- package/dist/agentic/guides/modules/channel_imap.md +39 -0
- package/dist/agentic/guides/modules/checkout.md +60 -0
- package/dist/agentic/guides/modules/communication_channels.md +65 -0
- package/dist/agentic/guides/modules/configs.md +42 -0
- package/dist/agentic/guides/modules/content.md +39 -0
- package/dist/agentic/guides/modules/currencies.md +50 -0
- package/dist/agentic/guides/modules/customer_accounts.md +76 -0
- package/dist/agentic/guides/modules/customers.md +115 -0
- package/dist/agentic/guides/modules/dashboards.md +43 -0
- package/dist/agentic/guides/modules/data_sync.md +49 -0
- package/dist/agentic/guides/modules/dictionaries.md +46 -0
- package/dist/agentic/guides/modules/directory.md +49 -0
- package/dist/agentic/guides/modules/entities.md +46 -0
- package/dist/agentic/guides/modules/events.md +39 -0
- package/dist/agentic/guides/modules/feature_toggles.md +42 -0
- package/dist/agentic/guides/modules/gateway_stripe.md +39 -0
- package/dist/agentic/guides/modules/generators.md +39 -0
- package/dist/agentic/guides/modules/inbox_ops.md +59 -0
- package/dist/agentic/guides/modules/integrations.md +49 -0
- package/dist/agentic/guides/modules/messages.md +55 -0
- package/dist/agentic/guides/modules/notifications.md +41 -0
- package/dist/agentic/guides/modules/onboarding.md +41 -0
- package/dist/agentic/guides/modules/payment_gateways.md +52 -0
- package/dist/agentic/guides/modules/perspectives.md +42 -0
- package/dist/agentic/guides/modules/planner.md +49 -0
- package/dist/agentic/guides/modules/portal.md +39 -0
- package/dist/agentic/guides/modules/progress.md +48 -0
- package/dist/agentic/guides/modules/query_index.md +46 -0
- package/dist/agentic/guides/modules/record_locks.md +53 -0
- package/dist/agentic/guides/modules/resources.md +62 -0
- package/dist/agentic/guides/modules/sales.md +109 -0
- package/dist/agentic/guides/modules/scheduler.md +46 -0
- package/dist/agentic/guides/modules/search.md +39 -0
- package/dist/agentic/guides/modules/security.md +67 -0
- package/dist/agentic/guides/modules/shipping_carriers.md +51 -0
- package/dist/agentic/guides/modules/sso.md +59 -0
- package/dist/agentic/guides/modules/staff.md +88 -0
- package/dist/agentic/guides/modules/storage_s3.md +39 -0
- package/dist/agentic/guides/modules/sync_akeneo.md +39 -0
- package/dist/agentic/guides/modules/sync_excel.md +41 -0
- package/dist/agentic/guides/modules/system_status_overlays.md +39 -0
- package/dist/agentic/guides/modules/translations.md +44 -0
- package/dist/agentic/guides/modules/webhooks.md +54 -0
- package/dist/agentic/guides/modules/workflows.md +73 -0
- package/dist/agentic/shared/AGENTS.md.template +21 -23
- package/dist/agentic/shared/ai/agentic.config.json +61 -0
- package/dist/agentic/shared/ai/skills/om-auto-continue-pr/SKILL.md +16 -318
- package/dist/agentic/shared/ai/skills/om-auto-continue-pr-loop/SKILL.md +16 -581
- package/dist/agentic/shared/ai/skills/om-auto-create-pr/SKILL.md +16 -397
- package/dist/agentic/shared/ai/skills/om-auto-create-pr-loop/SKILL.md +16 -738
- package/dist/agentic/shared/ai/skills/om-auto-fix-issue/SKILL.md +27 -0
- package/dist/agentic/shared/ai/skills/om-auto-review-pr/SKILL.md +16 -565
- package/dist/agentic/shared/ai/skills/om-help/references/skills-catalog.md +1 -1
- package/dist/agentic/shared/ai/skills/om-implement-spec/SKILL.md +1 -1
- package/dist/agentic/shared/ai/skills/om-integration-builder/SKILL.md +1 -1
- package/dist/agentic/shared/ai/skills/om-integration-builder/STANDALONE.md +34 -0
- package/dist/agentic/shared/ai/skills/om-module-scaffold/references/naming-conventions.md +4 -14
- package/dist/agentic/shared/ai/skills/om-troubleshooter/SKILL.md +10 -6
- package/dist/agentic/shared/ai/skills/tiers.json +47 -0
- package/dist/agentic/shared/ai/skills/tiers.schema.json +73 -0
- package/dist/agentic/shared/ai/trackers/github.md +376 -0
- package/dist/agentic/shared/scripts/install-skills.sh +483 -0
- package/dist/lib/__integration__/TC-INT-008.spec.js +29 -1
- package/dist/lib/__integration__/TC-INT-008.spec.js.map +2 -2
- package/dist/lib/agentic-setup.js +110 -40
- package/dist/lib/agentic-setup.js.map +2 -2
- package/dist/lib/auto-spawn-workers.js +8 -1
- package/dist/lib/auto-spawn-workers.js.map +2 -2
- package/dist/lib/deploy/railway/env.js +1 -1
- package/dist/lib/deploy/railway/env.js.map +2 -2
- package/dist/lib/events-single-delivery.js +27 -0
- package/dist/lib/events-single-delivery.js.map +7 -0
- package/dist/lib/generators/extensions/injection-widgets.js +144 -5
- package/dist/lib/generators/extensions/injection-widgets.js.map +2 -2
- package/dist/lib/generators/module-facts-discovery.js +46 -0
- package/dist/lib/generators/module-facts-discovery.js.map +7 -0
- package/dist/lib/generators/module-facts.js +740 -0
- package/dist/lib/generators/module-facts.js.map +7 -0
- package/dist/lib/generators/module-registry.js +263 -55
- package/dist/lib/generators/module-registry.js.map +3 -3
- package/dist/lib/init-secrets.js +11 -4
- package/dist/lib/init-secrets.js.map +2 -2
- package/dist/lib/module-install-args.js +9 -1
- package/dist/lib/module-install-args.js.map +2 -2
- package/dist/lib/module-install.js +22 -11
- package/dist/lib/module-install.js.map +2 -2
- package/dist/lib/module-package.js +2 -2
- package/dist/lib/module-package.js.map +2 -2
- package/dist/lib/queue-worker-supervisor.js +95 -0
- package/dist/lib/queue-worker-supervisor.js.map +2 -2
- package/dist/lib/testing/integration.js +302 -7
- package/dist/lib/testing/integration.js.map +3 -3
- package/dist/lib/utils.js +1 -1
- package/dist/lib/utils.js.map +2 -2
- package/dist/lib/worker-connection-budget.js +63 -0
- package/dist/lib/worker-connection-budget.js.map +7 -0
- package/dist/lib/worker-job-handler.js +13 -1
- package/dist/lib/worker-job-handler.js.map +2 -2
- package/dist/mercato.js +74 -16
- package/dist/mercato.js.map +2 -2
- package/jest.config.cjs +1 -1
- package/package.json +15 -13
- package/src/__tests__/mercato.test.ts +36 -0
- package/src/lib/__integration__/TC-INT-008.spec.ts +41 -1
- package/src/lib/__tests__/auto-spawn-workers.test.ts +19 -0
- package/src/lib/__tests__/events-single-delivery.test.ts +68 -0
- package/src/lib/__tests__/init-secrets.test.ts +37 -6
- package/src/lib/__tests__/module-install-args.test.ts +23 -0
- package/src/lib/__tests__/module-install.test.ts +66 -1
- package/src/lib/__tests__/module-package.test.ts +27 -0
- package/src/lib/__tests__/queue-worker-supervisor.test.ts +42 -0
- package/src/lib/__tests__/release-notes-retired.test.ts +33 -0
- package/src/lib/__tests__/worker-connection-budget.test.ts +115 -0
- package/src/lib/__tests__/worker-job-handler.test.ts +28 -0
- package/src/lib/agentic-setup.ts +162 -42
- package/src/lib/auto-spawn-workers.ts +8 -0
- package/src/lib/deploy/railway/env.ts +1 -1
- package/src/lib/events-single-delivery.ts +69 -0
- package/src/lib/generators/__tests__/module-facts.auth-source.test.ts +83 -0
- package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +69 -0
- package/src/lib/generators/__tests__/module-facts.customers.fixture.test.ts +69 -0
- package/src/lib/generators/__tests__/module-facts.discovery.test.ts +88 -0
- package/src/lib/generators/__tests__/module-facts.malformed.test.ts +76 -0
- package/src/lib/generators/__tests__/module-subset.test.ts +47 -46
- package/src/lib/generators/__tests__/output-snapshots.test.ts +2 -0
- package/src/lib/generators/__tests__/structural-contracts.test.ts +67 -5
- package/src/lib/generators/extensions/injection-widgets.ts +173 -4
- package/src/lib/generators/module-facts-discovery.ts +58 -0
- package/src/lib/generators/module-facts.ts +998 -0
- package/src/lib/generators/module-registry.ts +299 -47
- package/src/lib/init-secrets.ts +25 -6
- package/src/lib/module-install-args.ts +12 -1
- package/src/lib/module-install.ts +25 -9
- package/src/lib/module-package.ts +2 -2
- package/src/lib/queue-worker-supervisor.ts +105 -0
- package/src/lib/testing/__tests__/integration.test.ts +292 -6
- package/src/lib/testing/integration.ts +369 -4
- package/src/lib/utils.ts +1 -0
- package/src/lib/worker-connection-budget.ts +131 -0
- package/src/lib/worker-job-handler.ts +13 -1
- package/src/mercato.ts +118 -13
- package/dist/agentic/shared/ai/skills/om-auto-continue-pr/STANDALONE.md +0 -98
- package/dist/agentic/shared/ai/skills/om-auto-create-pr/STANDALONE.md +0 -98
- package/dist/agentic/shared/ai/skills/om-auto-fix-github/SKILL.md +0 -419
- package/dist/agentic/shared/ai/skills/om-auto-fix-github/STANDALONE.md +0 -98
- package/dist/agentic/shared/ai/skills/om-auto-review-pr/STANDALONE.md +0 -98
- package/dist/agentic/shared/ai/skills/om-code-review/SKILL.md +0 -144
- package/dist/agentic/shared/ai/skills/om-code-review/references/review-checklist.md +0 -83
- package/dist/agentic/shared/ai/skills/om-integration-tests/SKILL.md +0 -279
- package/dist/agentic/shared/ai/skills/om-prepare-issue/SKILL.md +0 -202
- package/dist/agentic/shared/ai/skills/om-spec-writing/SKILL.md +0 -85
- package/dist/agentic/shared/ai/skills/om-spec-writing/references/spec-checklist.md +0 -67
- package/dist/agentic/shared/ai/skills/om-spec-writing/references/spec-template.md +0 -86
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,4 +1,59 @@
|
|
|
1
|
-
[build:cli] found
|
|
1
|
+
[build:cli] found 93 entry points
|
|
2
2
|
Copied create-app/agentic/ → dist/agentic/
|
|
3
|
-
Discovered
|
|
3
|
+
Discovered 7 standalone guides → dist/agentic/guides/
|
|
4
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for ai_assistant
|
|
5
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for channel_gmail
|
|
6
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for channel_imap
|
|
7
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for checkout
|
|
8
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for content
|
|
9
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for api_docs
|
|
10
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for api_keys
|
|
11
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for attachments
|
|
12
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for audit_logs
|
|
13
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for auth
|
|
14
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for business_rules
|
|
15
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for catalog
|
|
16
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for communication_channels
|
|
17
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for configs
|
|
18
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for currencies
|
|
19
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for customer_accounts
|
|
20
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for customers
|
|
21
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for dashboards
|
|
22
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for data_sync
|
|
23
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for dictionaries
|
|
24
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for directory
|
|
25
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for entities
|
|
26
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for feature_toggles
|
|
27
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for inbox_ops
|
|
28
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for integrations
|
|
29
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for messages
|
|
30
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for notifications
|
|
31
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for payment_gateways
|
|
32
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for perspectives
|
|
33
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for planner
|
|
34
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for portal
|
|
35
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for progress
|
|
36
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for query_index
|
|
37
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for resources
|
|
38
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for sales
|
|
39
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for shipping_carriers
|
|
40
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for staff
|
|
41
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for sync_excel
|
|
42
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for translations
|
|
43
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for workflows
|
|
44
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for record_locks
|
|
45
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for security
|
|
46
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for sso
|
|
47
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for system_status_overlays
|
|
48
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for events
|
|
49
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for gateway_stripe
|
|
50
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for onboarding
|
|
51
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for scheduler
|
|
52
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for search
|
|
53
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for generators
|
|
54
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for storage_s3
|
|
55
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for sync_akeneo
|
|
56
|
+
[module-facts] module registry unavailable (registryPath not provided); API route auth omitted for webhooks
|
|
57
|
+
Generated 53 module fact-sheets → dist/agentic/guides/modules/
|
|
58
|
+
Wrote 9 legacy core.<module>.md redirect stubs → dist/agentic/guides/
|
|
4
59
|
[build:cli] built successfully
|
package/build.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { chmodSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync } from 'node:fs'
|
|
1
|
+
import { chmodSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
2
2
|
import { dirname, join } from 'node:path'
|
|
3
|
-
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
4
4
|
import { atomicWriteFileSync } from '../../scripts/lib/add-js-extension.mjs'
|
|
5
5
|
import { buildPackage } from '../../scripts/build-package.mjs'
|
|
6
6
|
|
|
@@ -35,6 +35,18 @@ await buildPackage(packageDir, {
|
|
|
35
35
|
const guidesDestDir = join(outdir, 'agentic', 'guides')
|
|
36
36
|
mkdirSync(guidesDestDir, { recursive: true })
|
|
37
37
|
|
|
38
|
+
// Clean stale per-module artifacts before regenerating so an incremental dist never
|
|
39
|
+
// retains a removed module's full guide or fact-sheet — a removed `core.<module>.md`
|
|
40
|
+
// (two-dot, per-module) must come back as a redirect stub, not linger as a full guide.
|
|
41
|
+
// Mirrors packages/create-app/build.mjs; the conceptual `module-system.md` and the
|
|
42
|
+
// single-dot package guides are re-copied/re-discovered below.
|
|
43
|
+
rmSync(join(guidesDestDir, 'modules'), { recursive: true, force: true })
|
|
44
|
+
for (const entry of readdirSync(guidesDestDir)) {
|
|
45
|
+
if (/^core\..+\.md$/.test(entry)) {
|
|
46
|
+
rmSync(join(guidesDestDir, entry))
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
38
50
|
let guidesFound = 0
|
|
39
51
|
for (const pkg of readdirSync(packagesDir)) {
|
|
40
52
|
const guideSource = join(packagesDir, pkg, 'agentic', 'standalone-guide.md')
|
|
@@ -58,5 +70,71 @@ await buildPackage(packageDir, {
|
|
|
58
70
|
if (guidesFound > 0) {
|
|
59
71
|
console.log(`Discovered ${guidesFound} standalone guides → dist/agentic/guides/`)
|
|
60
72
|
}
|
|
73
|
+
|
|
74
|
+
// Generate per-module fact-sheets (Layer 2) for every package-provided module via
|
|
75
|
+
// the freshly built ts-morph extractor + resolver-routed discovery, so
|
|
76
|
+
// `mercato agentic:init` bundles the same guides as a create-mercato-app scaffold
|
|
77
|
+
// (packages/create-app/build.mjs). Discovery goes through the resolver, never a
|
|
78
|
+
// hardcoded packages/* path (.ai/lessons.md §161-169).
|
|
79
|
+
const { extractAllModuleFacts, renderModuleFactsJson, MODULE_FACTS_ALLOWLIST } = await import(
|
|
80
|
+
pathToFileURL(join(outdir, 'lib', 'generators', 'module-facts.js')).href
|
|
81
|
+
)
|
|
82
|
+
const { discoverPackageModuleSources } = await import(
|
|
83
|
+
pathToFileURL(join(outdir, 'lib', 'generators', 'module-facts-discovery.js')).href
|
|
84
|
+
)
|
|
85
|
+
const { createResolver } = await import(pathToFileURL(join(outdir, 'lib', 'resolver.js')).href)
|
|
86
|
+
|
|
87
|
+
const sources = discoverPackageModuleSources(createResolver(join(packagesDir, '..')))
|
|
88
|
+
if (sources.length > 0) {
|
|
89
|
+
const registryPath = join(packagesDir, '..', 'apps', 'mercato', '.mercato', 'generated', 'modules.runtime.generated.ts')
|
|
90
|
+
let coreVersion = null
|
|
91
|
+
try {
|
|
92
|
+
coreVersion = JSON.parse(readFileSync(join(packagesDir, 'core', 'package.json'), 'utf8')).version ?? null
|
|
93
|
+
} catch {
|
|
94
|
+
coreVersion = null
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const { factsByModule, markdownByModule, warnings } = extractAllModuleFacts({
|
|
98
|
+
sources,
|
|
99
|
+
registryPath: existsSync(registryPath) ? registryPath : null,
|
|
100
|
+
coreVersion,
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const modulesGuidesDir = join(guidesDestDir, 'modules')
|
|
104
|
+
mkdirSync(modulesGuidesDir, { recursive: true })
|
|
105
|
+
for (const [moduleId, markdown] of Object.entries(markdownByModule)) {
|
|
106
|
+
writeFileSync(join(modulesGuidesDir, `${moduleId}.md`), markdown)
|
|
107
|
+
}
|
|
108
|
+
writeFileSync(join(guidesDestDir, 'module-facts.json'), renderModuleFactsJson(factsByModule))
|
|
109
|
+
|
|
110
|
+
for (const warning of warnings) console.warn(warning)
|
|
111
|
+
console.log(`Generated ${Object.keys(markdownByModule).length} module fact-sheets → dist/agentic/guides/modules/`)
|
|
112
|
+
|
|
113
|
+
// BC bridge (spec §7 generated-file contract): the legacy hand-written guides
|
|
114
|
+
// existed only for the historical allowlisted modules, so redirect stubs are
|
|
115
|
+
// emitted for that set alone — never for auto-discovered modules that never had a
|
|
116
|
+
// `core.<module>.md`. Fresh scaffolds never link these names; they exist only for
|
|
117
|
+
// apps upgrading in place.
|
|
118
|
+
const bundled = new Set(Object.keys(markdownByModule))
|
|
119
|
+
let stubsWritten = 0
|
|
120
|
+
for (const moduleId of MODULE_FACTS_ALLOWLIST) {
|
|
121
|
+
if (!bundled.has(moduleId)) continue
|
|
122
|
+
const legacyGuidePath = join(guidesDestDir, `core.${moduleId}.md`)
|
|
123
|
+
if (!existsSync(legacyGuidePath)) {
|
|
124
|
+
writeFileSync(
|
|
125
|
+
legacyGuidePath,
|
|
126
|
+
`# core.${moduleId} — moved\n\n` +
|
|
127
|
+
`> This guide has moved. See [\`modules/${moduleId}.md\`](modules/${moduleId}.md) for the generated ` +
|
|
128
|
+
`\`${moduleId}\` fact-sheet, and [\`module-system.md\`](module-system.md) for conceptual module guidance.\n`,
|
|
129
|
+
)
|
|
130
|
+
stubsWritten++
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (stubsWritten > 0) {
|
|
134
|
+
console.log(`Wrote ${stubsWritten} legacy core.<module>.md redirect stubs → dist/agentic/guides/`)
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
console.warn('[module-facts] no package modules discovered; skipping fact-sheet generation')
|
|
138
|
+
}
|
|
61
139
|
},
|
|
62
140
|
})
|
|
@@ -1,101 +1,3 @@
|
|
|
1
|
-
#
|
|
1
|
+
# core.auth — moved
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## RBAC Implementation
|
|
6
|
-
|
|
7
|
-
### Two-Layer Model
|
|
8
|
-
|
|
9
|
-
1. **Role ACLs** — features assigned to roles (admin, employee, etc.)
|
|
10
|
-
2. **User ACLs** — per-user overrides (additional features or restrictions)
|
|
11
|
-
|
|
12
|
-
Effective permissions = Role features + User-specific features.
|
|
13
|
-
|
|
14
|
-
### Declaring Features
|
|
15
|
-
|
|
16
|
-
Every module MUST declare features in `acl.ts` and wire them in `setup.ts`:
|
|
17
|
-
|
|
18
|
-
```typescript
|
|
19
|
-
// src/modules/<your_module>/acl.ts
|
|
20
|
-
export const features = [
|
|
21
|
-
'your_module.view',
|
|
22
|
-
'your_module.create',
|
|
23
|
-
'your_module.update',
|
|
24
|
-
'your_module.delete',
|
|
25
|
-
]
|
|
26
|
-
|
|
27
|
-
// src/modules/<your_module>/setup.ts
|
|
28
|
-
import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'
|
|
29
|
-
|
|
30
|
-
export const setup: ModuleSetupConfig = {
|
|
31
|
-
defaultRoleFeatures: {
|
|
32
|
-
superadmin: ['your_module.*'],
|
|
33
|
-
admin: ['your_module.*'],
|
|
34
|
-
user: ['your_module.view'],
|
|
35
|
-
},
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
### Feature Naming Convention
|
|
40
|
-
|
|
41
|
-
Features follow the `<module>.<action>` pattern (e.g., `users.view`, `users.edit`).
|
|
42
|
-
|
|
43
|
-
### Declarative Guards
|
|
44
|
-
|
|
45
|
-
Prefer declarative guards in page and API metadata:
|
|
46
|
-
|
|
47
|
-
```typescript
|
|
48
|
-
export const metadata = {
|
|
49
|
-
requireAuth: true,
|
|
50
|
-
requireRoles: ['admin'],
|
|
51
|
-
requireFeatures: ['users.manage'],
|
|
52
|
-
}
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
### Server-Side Checks
|
|
56
|
-
|
|
57
|
-
```typescript
|
|
58
|
-
const rbacService = container.resolve('rbacService')
|
|
59
|
-
const hasAccess = await rbacService.userHasAllFeatures(
|
|
60
|
-
userId,
|
|
61
|
-
['your_module.view'],
|
|
62
|
-
{ tenantId, organizationId }
|
|
63
|
-
)
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
### Wildcards
|
|
67
|
-
|
|
68
|
-
Wildcards are first-class ACL grants: `module.*` and `*` satisfy matching concrete features. When your code inspects raw granted feature arrays (instead of calling `rbacService`), use the shared wildcard-aware matchers (`matchFeature`, `hasFeature`, `hasAllFeatures`) — never use `includes(...)`.
|
|
69
|
-
|
|
70
|
-
### Special Flags
|
|
71
|
-
|
|
72
|
-
- `isSuperAdmin` — bypasses all feature checks
|
|
73
|
-
- Organization visibility list — restricts which organizations a user can access
|
|
74
|
-
|
|
75
|
-
## Security Rules
|
|
76
|
-
|
|
77
|
-
- Hash passwords with `bcryptjs` (cost >= 10)
|
|
78
|
-
- Never log credentials
|
|
79
|
-
- Return minimal auth error messages — never reveal whether an email exists
|
|
80
|
-
- Use `findWithDecryption` / `findOneWithDecryption` for user queries
|
|
81
|
-
|
|
82
|
-
## Authentication Flow
|
|
83
|
-
|
|
84
|
-
1. User submits credentials via `POST /api/auth/session`
|
|
85
|
-
2. Password verified with bcryptjs
|
|
86
|
-
3. JWT session token issued
|
|
87
|
-
4. Session attached to requests via middleware
|
|
88
|
-
|
|
89
|
-
## Subscribing to Auth Events
|
|
90
|
-
|
|
91
|
-
```typescript
|
|
92
|
-
export const metadata = {
|
|
93
|
-
event: 'auth.user.created',
|
|
94
|
-
persistent: true,
|
|
95
|
-
id: 'your-module-user-created',
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export default async function handler(payload, ctx) {
|
|
99
|
-
// React to new user registration
|
|
100
|
-
}
|
|
101
|
-
```
|
|
3
|
+
> This guide has moved. See [`modules/auth.md`](modules/auth.md) for the generated `auth` fact-sheet, and [`module-system.md`](module-system.md) for conceptual module guidance.
|
|
@@ -1,79 +1,3 @@
|
|
|
1
|
-
#
|
|
1
|
+
# core.catalog — moved
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Pricing System
|
|
6
|
-
|
|
7
|
-
Never reimplement pricing logic. Use the catalog pricing service via DI:
|
|
8
|
-
|
|
9
|
-
```typescript
|
|
10
|
-
const pricingService = container.resolve('catalogPricingService')
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
- `selectBestPrice` — finds the best price for a given context (customer, channel, quantity)
|
|
14
|
-
- `resolvePriceVariantId` — resolves variant-level prices
|
|
15
|
-
- Register custom pricing resolvers with priority (higher = checked first):
|
|
16
|
-
|
|
17
|
-
```typescript
|
|
18
|
-
import { registerCatalogPricingResolver } from '@open-mercato/core/modules/catalog/lib/pricing'
|
|
19
|
-
registerCatalogPricingResolver(myResolver, { priority: 10 })
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
Price layers compose in order: base price → channel override → customer-specific → promotional.
|
|
23
|
-
|
|
24
|
-
The pipeline emits `catalog.pricing.resolve.before` and `catalog.pricing.resolve.after` events that your module can subscribe to.
|
|
25
|
-
|
|
26
|
-
## Data Model
|
|
27
|
-
|
|
28
|
-
| Entity | Purpose | Key Constraints |
|
|
29
|
-
|--------|---------|----------------|
|
|
30
|
-
| **Products** | Core items with media and descriptions | MUST have at least a name |
|
|
31
|
-
| **Categories** | Hierarchical product grouping | No circular parent-child references |
|
|
32
|
-
| **Variants** | Product variations (size, color) | MUST reference valid option schemas |
|
|
33
|
-
| **Prices** | Multi-tier with channel scoping | Use `selectBestPrice` for resolution |
|
|
34
|
-
| **Offers** | Time-limited promotions | MUST have valid date ranges |
|
|
35
|
-
| **Option Schemas** | Variant option type definitions | Cannot delete while variants reference them |
|
|
36
|
-
|
|
37
|
-
## Subscribing to Catalog Events
|
|
38
|
-
|
|
39
|
-
React to product lifecycle events in your module:
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
// src/modules/<your_module>/subscribers/product-updated.ts
|
|
43
|
-
export const metadata = {
|
|
44
|
-
event: 'catalog.product.updated',
|
|
45
|
-
persistent: true,
|
|
46
|
-
id: 'your-module-product-updated',
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export default async function handler(payload, ctx) {
|
|
50
|
-
// payload.resourceId = product ID
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Key events:
|
|
55
|
-
- `catalog.product.created` / `updated` / `deleted`
|
|
56
|
-
- `catalog.pricing.resolve.before` / `after` (excluded from workflow triggers)
|
|
57
|
-
|
|
58
|
-
## Extending Catalog UI
|
|
59
|
-
|
|
60
|
-
Use widget injection to add your module's UI into catalog pages:
|
|
61
|
-
|
|
62
|
-
```typescript
|
|
63
|
-
// src/modules/<your_module>/widgets/injection-table.ts
|
|
64
|
-
export const widgetInjections = {
|
|
65
|
-
'crud-form:catalog.catalog_product:fields': {
|
|
66
|
-
widgetId: 'your-module-product-fields',
|
|
67
|
-
priority: 100,
|
|
68
|
-
},
|
|
69
|
-
}
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
Common injection spots:
|
|
73
|
-
- `crud-form:catalog.catalog_product:fields` — product edit form
|
|
74
|
-
- `data-table:catalog.products:columns` — product list columns
|
|
75
|
-
- `data-table:catalog.products:row-actions` — product row actions
|
|
76
|
-
|
|
77
|
-
## Using Catalog in Sales
|
|
78
|
-
|
|
79
|
-
When building sales-related features, use the catalog pricing service to resolve prices rather than reading price entities directly. This ensures channel scoping, customer-specific pricing, and promotional offers are applied correctly.
|
|
3
|
+
> This guide has moved. See [`modules/catalog.md`](modules/catalog.md) for the generated `catalog` fact-sheet, and [`module-system.md`](module-system.md) for conceptual module guidance.
|
|
@@ -1,43 +1,3 @@
|
|
|
1
|
-
#
|
|
1
|
+
# core.currencies — moved
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Key Rules
|
|
6
|
-
|
|
7
|
-
1. **Store amounts with 4 decimal precision** — never truncate to 2 decimals internally
|
|
8
|
-
2. **Use date-based exchange rates** — always resolve rates for the transaction date, not the "current" rate
|
|
9
|
-
3. **Record both currencies** — dual recording (transaction currency + base currency) is mandatory for reporting
|
|
10
|
-
4. **Calculate realized gains/losses** on payment: `(payment rate - invoice rate) × foreign amount`
|
|
11
|
-
5. **Never hard-delete exchange rates** — they are historical reference data
|
|
12
|
-
|
|
13
|
-
## Multi-Currency Transaction Pattern
|
|
14
|
-
|
|
15
|
-
When processing multi-currency transactions (e.g., sales invoice in EUR with USD base):
|
|
16
|
-
|
|
17
|
-
1. Retrieve the exchange rate for the transaction date
|
|
18
|
-
2. Generate the document in the transaction currency
|
|
19
|
-
3. Calculate the base currency equivalent: `foreign amount × rate`
|
|
20
|
-
4. Store both amounts on the document
|
|
21
|
-
5. On payment: calculate realized gain/loss from rate difference
|
|
22
|
-
6. Report in both transaction and base currencies
|
|
23
|
-
|
|
24
|
-
## Data Model
|
|
25
|
-
|
|
26
|
-
| Entity | Table | Purpose |
|
|
27
|
-
|--------|-------|---------|
|
|
28
|
-
| **Currency** | `currency` | Currency master data (code, name, symbol) |
|
|
29
|
-
| **Exchange Rate** | `exchange_rate` | Daily exchange rates per currency pair |
|
|
30
|
-
|
|
31
|
-
## Adding a New Currency
|
|
32
|
-
|
|
33
|
-
1. Add the currency record via the admin UI or `seedDefaults` hook in your `setup.ts`
|
|
34
|
-
2. Ensure exchange rates exist for the currency pair at required dates
|
|
35
|
-
3. Verify all sales/pricing logic resolves the new currency correctly
|
|
36
|
-
|
|
37
|
-
## Using Currencies in Your Module
|
|
38
|
-
|
|
39
|
-
When your module deals with monetary amounts:
|
|
40
|
-
- Store the currency code alongside the amount
|
|
41
|
-
- Reference the currencies module for exchange rate lookups
|
|
42
|
-
- Use the transaction date for rate resolution, not the current date
|
|
43
|
-
- Store both foreign and base amounts for reporting
|
|
3
|
+
> This guide has moved. See [`modules/currencies.md`](modules/currencies.md) for the generated `currencies` fact-sheet, and [`module-system.md`](module-system.md) for conceptual module guidance.
|
|
@@ -1,129 +1,3 @@
|
|
|
1
|
-
#
|
|
1
|
+
# core.customer_accounts — moved
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Portal Authentication
|
|
6
|
-
|
|
7
|
-
### Login Flow
|
|
8
|
-
1. Customer submits credentials via `POST /api/login`
|
|
9
|
-
2. Password verified with bcryptjs, lockout checked (5 attempts → 15 min lock)
|
|
10
|
-
3. JWT issued with customer claims (`type: 'customer'`, features, CRM links)
|
|
11
|
-
4. Two cookies set: `customer_auth_token` (JWT, 8h) + `customer_session_token` (raw, 30d)
|
|
12
|
-
|
|
13
|
-
### Other Auth Methods
|
|
14
|
-
- **Signup**: `POST /api/signup` — self-registration with email verification
|
|
15
|
-
- **Magic Link**: `POST /api/magic-link/request` + `/verify` — passwordless login (15 min TTL)
|
|
16
|
-
- **Password Reset**: `POST /api/password/reset-request` + `/reset-confirm` (60 min TTL)
|
|
17
|
-
- **Invitation**: Admin invites user → `POST /api/invitations/accept` (72h TTL)
|
|
18
|
-
|
|
19
|
-
## Customer RBAC
|
|
20
|
-
|
|
21
|
-
### Two-Layer Model (mirrors staff RBAC)
|
|
22
|
-
1. **Role ACLs** — features assigned to roles
|
|
23
|
-
2. **User ACLs** — per-user overrides (takes precedence if present)
|
|
24
|
-
|
|
25
|
-
### Default Roles (seeded on tenant creation)
|
|
26
|
-
| Role | Features | Portal Admin |
|
|
27
|
-
|------|----------|-------------|
|
|
28
|
-
| Portal Admin | `portal.*` | Yes |
|
|
29
|
-
| Buyer | Orders, quotes, catalog, account | No |
|
|
30
|
-
| Viewer | Read-only orders, invoices, catalog | No |
|
|
31
|
-
|
|
32
|
-
### Feature Convention
|
|
33
|
-
Portal features use `portal.<area>.<action>` naming (e.g., `portal.orders.view`, `portal.catalog.view`).
|
|
34
|
-
|
|
35
|
-
### Cross-Module Feature Merging
|
|
36
|
-
Your module can declare `defaultCustomerRoleFeatures` in `setup.ts`. During tenant setup, these are merged into the corresponding customer role ACLs:
|
|
37
|
-
|
|
38
|
-
```typescript
|
|
39
|
-
// src/modules/<your_module>/setup.ts
|
|
40
|
-
export const setup: ModuleSetupConfig = {
|
|
41
|
-
defaultCustomerRoleFeatures: {
|
|
42
|
-
portal_admin: ['portal.your_feature.*'],
|
|
43
|
-
buyer: ['portal.your_feature.view'],
|
|
44
|
-
},
|
|
45
|
-
}
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Using Customer Auth in Your Module
|
|
49
|
-
|
|
50
|
-
### Server Components (pages)
|
|
51
|
-
```typescript
|
|
52
|
-
import { getCustomerAuthFromCookies } from '@open-mercato/core/modules/customer_accounts/lib/customerAuthServer'
|
|
53
|
-
|
|
54
|
-
const auth = await getCustomerAuthFromCookies()
|
|
55
|
-
if (!auth) redirect('/login')
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### API Routes
|
|
59
|
-
```typescript
|
|
60
|
-
import { requireCustomerAuth, requireCustomerFeature } from '@open-mercato/core/modules/customer_accounts/lib/customerAuth'
|
|
61
|
-
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
62
|
-
import type { CustomerRbacService } from '@open-mercato/core/modules/customer_accounts/services/customerRbacService'
|
|
63
|
-
|
|
64
|
-
// In your API handler:
|
|
65
|
-
const auth = await requireCustomerAuth(request) // throws 401 if not authenticated
|
|
66
|
-
const container = await createRequestContainer()
|
|
67
|
-
const customerRbacService = container.resolve('customerRbacService') as CustomerRbacService
|
|
68
|
-
// Re-resolves ACL on every request so role/feature revocation is immediate
|
|
69
|
-
await requireCustomerFeature(auth, ['portal.orders.view'], customerRbacService) // throws 403 if missing
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
### RBAC Service
|
|
73
|
-
```typescript
|
|
74
|
-
const rbacService = container.resolve('customerRbacService')
|
|
75
|
-
const hasAccess = await rbacService.userHasAllFeatures(
|
|
76
|
-
userId, ['portal.orders.view'], { tenantId, organizationId }
|
|
77
|
-
)
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
## Portal Page Guards
|
|
81
|
-
|
|
82
|
-
Use declarative metadata for portal pages:
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
85
|
-
export const metadata = {
|
|
86
|
-
requireCustomerAuth: true,
|
|
87
|
-
requireCustomerFeatures: ['portal.orders.view'],
|
|
88
|
-
}
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
## Subscribing to Customer Events
|
|
92
|
-
|
|
93
|
-
| Event | When |
|
|
94
|
-
|-------|------|
|
|
95
|
-
| `customer_accounts.user.created` | New customer signup |
|
|
96
|
-
| `customer_accounts.user.updated` | Profile updated |
|
|
97
|
-
| `customer_accounts.user.locked` | Account locked after failed logins |
|
|
98
|
-
| `customer_accounts.login.success` | Successful login |
|
|
99
|
-
| `customer_accounts.invitation.accepted` | Invitation accepted |
|
|
100
|
-
|
|
101
|
-
```typescript
|
|
102
|
-
export const metadata = {
|
|
103
|
-
event: 'customer_accounts.user.created',
|
|
104
|
-
persistent: true,
|
|
105
|
-
id: 'your-module-customer-signup',
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export default async function handler(payload, ctx) {
|
|
109
|
-
// React to customer signup — e.g., create default preferences
|
|
110
|
-
}
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
## CRM Auto-Linking
|
|
114
|
-
|
|
115
|
-
When a customer signs up, the module automatically searches for a matching CRM person by email and links them (`personEntityId`). The reverse also works — creating a CRM person auto-links to an existing customer user.
|
|
116
|
-
|
|
117
|
-
## Widget Injection Spots
|
|
118
|
-
|
|
119
|
-
| Spot | Widget | Purpose |
|
|
120
|
-
|------|--------|---------|
|
|
121
|
-
| `crud-form:customers:customer_person_profile:fields` | Account status | Shows portal account status on CRM person detail |
|
|
122
|
-
| `crud-form:customers:customer_company_profile:fields` | Company users | Shows portal users linked to a CRM company |
|
|
123
|
-
|
|
124
|
-
## Security Notes
|
|
125
|
-
|
|
126
|
-
- All public endpoints are rate-limited (per-email + per-IP)
|
|
127
|
-
- Tokens stored as SHA-256 hashes — raw tokens never persisted
|
|
128
|
-
- Emails use deterministic hash for lookups (`hashForLookup`)
|
|
129
|
-
- Error messages never confirm whether an email is registered
|
|
3
|
+
> This guide has moved. See [`modules/customer_accounts.md`](modules/customer_accounts.md) for the generated `customer_accounts` fact-sheet, and [`module-system.md`](module-system.md) for conceptual module guidance.
|
|
@@ -1,138 +1,3 @@
|
|
|
1
|
-
#
|
|
1
|
+
# core.customers — moved
|
|
2
2
|
|
|
3
|
-
This
|
|
4
|
-
|
|
5
|
-
## CRUD API Pattern
|
|
6
|
-
|
|
7
|
-
Use `makeCrudRoute` with `indexer: { entityType }` for query index coverage:
|
|
8
|
-
|
|
9
|
-
```typescript
|
|
10
|
-
// src/modules/<your_module>/api/get/<entities>.ts
|
|
11
|
-
import { makeCrudRoute } from '@open-mercato/shared/lib/crud/make-crud-route'
|
|
12
|
-
import { YourEntity } from '../../entities/YourEntity'
|
|
13
|
-
|
|
14
|
-
const handler = makeCrudRoute({
|
|
15
|
-
entity: YourEntity,
|
|
16
|
-
entityId: 'your_module.your_entity',
|
|
17
|
-
operations: ['list', 'detail'],
|
|
18
|
-
indexer: { entityType: 'your_module.your_entity' },
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
export default handler
|
|
22
|
-
export const openApi = { summary: 'List and retrieve entities', tags: ['Your Module'] }
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Key points:
|
|
26
|
-
- Always set `indexer: { entityType }` — keeps custom entities indexed
|
|
27
|
-
- Wire custom field helpers for create/update if your module supports custom fields
|
|
28
|
-
- Export `openApi` on every API route file
|
|
29
|
-
|
|
30
|
-
## Undoable Commands Pattern
|
|
31
|
-
|
|
32
|
-
All write operations should use the Command pattern with undo support:
|
|
33
|
-
|
|
34
|
-
```typescript
|
|
35
|
-
import { registerCommand } from '@open-mercato/shared/lib/commands'
|
|
36
|
-
import { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'
|
|
37
|
-
|
|
38
|
-
registerCommand('your_module.entity.create', {
|
|
39
|
-
async execute(payload, ctx) {
|
|
40
|
-
// 1. Create entity
|
|
41
|
-
// 2. Capture snapshot for undo: extractUndoPayload(entity)
|
|
42
|
-
// 3. Side effects: emitCrudSideEffects({ indexer: { entityType, cacheAliases } })
|
|
43
|
-
},
|
|
44
|
-
async undo(payload, ctx) {
|
|
45
|
-
// 1. Restore from snapshot
|
|
46
|
-
// 2. Side effects: emitCrudUndoSideEffects({ indexer: { entityType, cacheAliases } })
|
|
47
|
-
},
|
|
48
|
-
})
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
Key points:
|
|
52
|
-
- Include `indexer: { entityType, cacheAliases }` in both `emitCrudSideEffects` and `emitCrudUndoSideEffects`
|
|
53
|
-
- Capture custom field snapshots in `before`/`after` payloads (`snapshot.custom`)
|
|
54
|
-
- Restore custom fields via `buildCustomFieldResetMap(before.custom, after.custom)` in undo
|
|
55
|
-
|
|
56
|
-
## Custom Field Integration
|
|
57
|
-
|
|
58
|
-
```typescript
|
|
59
|
-
import { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
- Pass `{ transform }` to normalize values (e.g., `normalizeCustomFieldSubmitValue`)
|
|
63
|
-
- Works for both `cf_` and `cf:` prefixed keys
|
|
64
|
-
- Pass `entityIds` to form helpers so correct custom-field sets are loaded
|
|
65
|
-
- If your module ships default custom fields, declare them in `ce.ts` via `entities[].fields`
|
|
66
|
-
|
|
67
|
-
## Search Configuration
|
|
68
|
-
|
|
69
|
-
Declare in `search.ts` with all three strategies:
|
|
70
|
-
|
|
71
|
-
```typescript
|
|
72
|
-
import type { SearchModuleConfig } from '@open-mercato/shared/modules/search'
|
|
73
|
-
|
|
74
|
-
export const searchConfig: SearchModuleConfig = {
|
|
75
|
-
entities: {
|
|
76
|
-
'your_module.your_entity': {
|
|
77
|
-
fields: ['name', 'description'], // Fulltext indexing
|
|
78
|
-
// fieldPolicy for sensitive field handling
|
|
79
|
-
// buildSource for vector embeddings
|
|
80
|
-
// formatResult for search result display
|
|
81
|
-
},
|
|
82
|
-
},
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
Key points:
|
|
87
|
-
- Use `fieldPolicy.excluded` for sensitive fields (passwords, tokens)
|
|
88
|
-
- Use `fieldPolicy.hashOnly` for PII needing exact-match only (email, phone)
|
|
89
|
-
- Always define `formatResult` for human-friendly search results
|
|
90
|
-
|
|
91
|
-
## Backend Page Structure
|
|
92
|
-
|
|
93
|
-
Follow this pattern for each page type:
|
|
94
|
-
|
|
95
|
-
| Page | Pattern | Key Features |
|
|
96
|
-
|------|---------|-------------|
|
|
97
|
-
| **List** | `DataTable` | Filters, search, export, row actions, pagination |
|
|
98
|
-
| **Create** | `CrudForm` mode=create | Fields, groups, custom fields, back link |
|
|
99
|
-
| **Detail/Edit** | `CrudForm` mode=edit or tabbed layout | Entity data, related entities, activities |
|
|
100
|
-
|
|
101
|
-
## Module Files Checklist
|
|
102
|
-
|
|
103
|
-
When scaffolding a new CRUD module, ensure all these files are present:
|
|
104
|
-
|
|
105
|
-
| File | Purpose |
|
|
106
|
-
|------|---------|
|
|
107
|
-
| `index.ts` | Module metadata |
|
|
108
|
-
| `acl.ts` | Feature-based permissions |
|
|
109
|
-
| `setup.ts` | Tenant init, default role features |
|
|
110
|
-
| `di.ts` | Awilix DI registrations |
|
|
111
|
-
| `events.ts` | Typed event declarations |
|
|
112
|
-
| `data/entities.ts` | MikroORM entity classes |
|
|
113
|
-
| `data/validators.ts` | Zod validation schemas |
|
|
114
|
-
| `search.ts` | Search indexing configuration |
|
|
115
|
-
| `ce.ts` | Custom entities / custom field sets |
|
|
116
|
-
|
|
117
|
-
Optional:
|
|
118
|
-
- `translations.ts` — translatable fields per entity
|
|
119
|
-
- `notifications.ts` — notification type definitions
|
|
120
|
-
- `cli.ts` — module CLI commands
|
|
121
|
-
|
|
122
|
-
## Entity Update Safety
|
|
123
|
-
|
|
124
|
-
When mutating entities across multiple phases that include queries:
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'
|
|
128
|
-
|
|
129
|
-
await withAtomicFlush(em, [
|
|
130
|
-
() => { record.name = 'New'; record.status = 'active' },
|
|
131
|
-
() => syncEntityTags(em, record, tags),
|
|
132
|
-
], { transaction: true })
|
|
133
|
-
|
|
134
|
-
// Side effects AFTER the atomic flush
|
|
135
|
-
await emitCrudSideEffects({ ... })
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
Never run `em.find`/`em.findOne` between scalar mutations and `em.flush()` without `withAtomicFlush` — changes will be silently lost. Cache invalidation must also stay outside `withAtomicFlush` and fire after commit, so the opt-in `OM_CACHE_SAFETY_ALWAYS_CONSISTENT` mode (default OFF) never serves stale or partially-committed reads.
|
|
3
|
+
> This guide has moved. See [`modules/customers.md`](modules/customers.md) for the generated `customers` fact-sheet, and [`module-system.md`](module-system.md) for conceptual module guidance.
|