@open-mercato/cli 0.7.1-develop.7136.1.0f76137a1d → 0.7.1-develop.7148.1.3076e5ccf7
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 +1 -1
- package/AGENTS.md +2 -0
- package/dist/agentic/guides/module-facts.json +128 -128
- package/dist/agentic/guides/module-facts.v2.json +128 -128
- package/dist/agentic/guides/modules/ai_assistant/index.md +1 -1
- package/dist/agentic/guides/modules/api_docs/index.md +1 -1
- package/dist/agentic/guides/modules/api_keys/index.md +1 -1
- package/dist/agentic/guides/modules/attachments/index.md +1 -1
- package/dist/agentic/guides/modules/audit_logs/index.md +1 -1
- package/dist/agentic/guides/modules/auth/index.md +1 -1
- package/dist/agentic/guides/modules/business_rules/index.md +1 -1
- package/dist/agentic/guides/modules/catalog/index.md +1 -1
- package/dist/agentic/guides/modules/channel_apns/index.md +1 -1
- package/dist/agentic/guides/modules/channel_discord/index.md +1 -1
- package/dist/agentic/guides/modules/channel_expo/index.md +1 -1
- package/dist/agentic/guides/modules/channel_fcm/index.md +1 -1
- package/dist/agentic/guides/modules/channel_gmail/index.md +1 -1
- package/dist/agentic/guides/modules/channel_imap/index.md +1 -1
- package/dist/agentic/guides/modules/checkout/index.md +1 -1
- package/dist/agentic/guides/modules/communication_channels/index.md +1 -1
- package/dist/agentic/guides/modules/configs/index.md +1 -1
- package/dist/agentic/guides/modules/content/index.md +1 -1
- package/dist/agentic/guides/modules/currencies/index.md +1 -1
- package/dist/agentic/guides/modules/customer_accounts/index.md +1 -1
- package/dist/agentic/guides/modules/customers/index.md +1 -1
- package/dist/agentic/guides/modules/dashboards/index.md +1 -1
- package/dist/agentic/guides/modules/data_sync/index.md +1 -1
- package/dist/agentic/guides/modules/design_system/index.md +1 -1
- package/dist/agentic/guides/modules/devices/index.md +1 -1
- package/dist/agentic/guides/modules/dictionaries/index.md +1 -1
- package/dist/agentic/guides/modules/directory/index.md +1 -1
- package/dist/agentic/guides/modules/documents/index.md +1 -1
- package/dist/agentic/guides/modules/entities/index.md +1 -1
- package/dist/agentic/guides/modules/eudr/index.md +1 -1
- package/dist/agentic/guides/modules/events/index.md +1 -1
- package/dist/agentic/guides/modules/feature_toggles/index.md +1 -1
- package/dist/agentic/guides/modules/gateway_stripe/index.md +1 -1
- package/dist/agentic/guides/modules/generators/index.md +1 -1
- package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
- package/dist/agentic/guides/modules/integrations/index.md +1 -1
- package/dist/agentic/guides/modules/messages/index.md +1 -1
- package/dist/agentic/guides/modules/notifications/index.md +1 -1
- package/dist/agentic/guides/modules/onboarding/index.md +1 -1
- package/dist/agentic/guides/modules/payment_gateways/index.md +1 -1
- package/dist/agentic/guides/modules/perspectives/index.md +1 -1
- package/dist/agentic/guides/modules/planner/index.md +1 -1
- package/dist/agentic/guides/modules/portal/index.md +1 -1
- package/dist/agentic/guides/modules/progress/index.md +1 -1
- package/dist/agentic/guides/modules/push_notifications/index.md +1 -1
- package/dist/agentic/guides/modules/query_index/index.md +1 -1
- package/dist/agentic/guides/modules/record_locks/index.md +1 -1
- package/dist/agentic/guides/modules/resources/index.md +1 -1
- package/dist/agentic/guides/modules/sales/index.md +1 -1
- package/dist/agentic/guides/modules/scheduler/index.md +1 -1
- package/dist/agentic/guides/modules/search/index.md +1 -1
- package/dist/agentic/guides/modules/security/index.md +1 -1
- package/dist/agentic/guides/modules/shipping_carriers/index.md +1 -1
- package/dist/agentic/guides/modules/sso/index.md +1 -1
- package/dist/agentic/guides/modules/staff/index.md +1 -1
- package/dist/agentic/guides/modules/storage_s3/index.md +1 -1
- package/dist/agentic/guides/modules/sync_akeneo/index.md +1 -1
- package/dist/agentic/guides/modules/sync_excel/index.md +1 -1
- package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
- package/dist/agentic/guides/modules/translations/index.md +1 -1
- package/dist/agentic/guides/modules/warranty_claims/index.md +1 -1
- package/dist/agentic/guides/modules/webhooks/index.md +1 -1
- package/dist/agentic/guides/modules/wms/index.md +1 -1
- package/dist/agentic/guides/modules/workflows/index.md +1 -1
- package/dist/agentic/guides/reference-module-facts.json +1 -1
- package/dist/agentic/guides/upstream/manifest.json +1 -1
- package/dist/agentic/shared/ai/review-checklist.md +1 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/migration-workflow.md +30 -2
- package/dist/lib/db/commands.js +106 -60
- package/dist/lib/db/commands.js.map +2 -2
- package/dist/lib/db/migration-reindex.js +104 -0
- package/dist/lib/db/migration-reindex.js.map +7 -0
- package/package.json +6 -6
- package/src/lib/db/__tests__/commands.migrate-reindex.test.ts +189 -0
- package/src/lib/db/__tests__/migration-reindex.test.ts +203 -0
- package/src/lib/db/commands.ts +132 -72
- package/src/lib/db/migration-reindex.ts +156 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'
|
|
4
|
+
import {
|
|
5
|
+
formatQueryIndexRebuildCommands,
|
|
6
|
+
readQueryIndexReindexDeclaration,
|
|
7
|
+
} from '@open-mercato/shared/lib/query/migration-reindex'
|
|
8
|
+
|
|
9
|
+
const MIGRATION_FILE_NAME_PATTERN = /^[A-Za-z0-9._-]+$/
|
|
10
|
+
const MIGRATION_EXTENSIONS = ['.ts', '.js', '.mjs', '.cjs']
|
|
11
|
+
|
|
12
|
+
export type AppliedMigration = {
|
|
13
|
+
moduleId: string
|
|
14
|
+
migrationsPath: string
|
|
15
|
+
name: string
|
|
16
|
+
filePath?: string | null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type MigrationReindexDeps = {
|
|
20
|
+
importModule: (filePath: string) => Promise<unknown>
|
|
21
|
+
fileExists?: (filePath: string) => boolean
|
|
22
|
+
onWarn?: (message: string) => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function isMigrationReindexEnabled(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
26
|
+
return parseBooleanWithDefault(env.OM_MIGRATION_REINDEX, true)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function resolveMigrationFilePath(
|
|
30
|
+
migrationsPath: string,
|
|
31
|
+
migrationName: string,
|
|
32
|
+
knownPath?: string | null,
|
|
33
|
+
fileExists: (filePath: string) => boolean = fs.existsSync,
|
|
34
|
+
): string | null {
|
|
35
|
+
if (knownPath && fileExists(knownPath)) return knownPath
|
|
36
|
+
const stem = path.basename(migrationName).replace(/\.(ts|js|mjs|cjs)$/i, '')
|
|
37
|
+
if (!stem || !MIGRATION_FILE_NAME_PATTERN.test(stem)) return null
|
|
38
|
+
for (const extension of MIGRATION_EXTENSIONS) {
|
|
39
|
+
const candidate = path.join(migrationsPath, `${stem}${extension}`)
|
|
40
|
+
if (fileExists(candidate)) return candidate
|
|
41
|
+
}
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function collectQueryIndexReindexEntityTypes(
|
|
46
|
+
applied: readonly AppliedMigration[],
|
|
47
|
+
deps: MigrationReindexDeps,
|
|
48
|
+
): Promise<string[]> {
|
|
49
|
+
const fileExists = deps.fileExists ?? fs.existsSync
|
|
50
|
+
const collected: string[] = []
|
|
51
|
+
for (const migration of applied) {
|
|
52
|
+
const filePath = resolveMigrationFilePath(
|
|
53
|
+
migration.migrationsPath,
|
|
54
|
+
migration.name,
|
|
55
|
+
migration.filePath,
|
|
56
|
+
fileExists,
|
|
57
|
+
)
|
|
58
|
+
if (!filePath) continue
|
|
59
|
+
let moduleExports: unknown
|
|
60
|
+
try {
|
|
61
|
+
moduleExports = await deps.importModule(filePath)
|
|
62
|
+
} catch (error) {
|
|
63
|
+
deps.onWarn?.(
|
|
64
|
+
`[query_index] Could not read reindex declarations from ${migration.moduleId}/${migration.name}: ${
|
|
65
|
+
error instanceof Error ? error.message : String(error)
|
|
66
|
+
}`,
|
|
67
|
+
)
|
|
68
|
+
continue
|
|
69
|
+
}
|
|
70
|
+
const declared = readQueryIndexReindexDeclaration(moduleExports, (rejected: unknown) => {
|
|
71
|
+
deps.onWarn?.(
|
|
72
|
+
`[query_index] Ignoring an invalid reindex declaration in ${migration.moduleId}/${migration.name}: ${
|
|
73
|
+
JSON.stringify(rejected) ?? String(rejected)
|
|
74
|
+
} is not a "module:entity" identifier, so its projection will NOT be rebuilt.`,
|
|
75
|
+
)
|
|
76
|
+
})
|
|
77
|
+
for (const entityType of declared) {
|
|
78
|
+
if (!collected.includes(entityType)) collected.push(entityType)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return collected
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function formatManualReindexInstructions(entityTypes: readonly string[]): string {
|
|
85
|
+
const commands = formatQueryIndexRebuildCommands(entityTypes)
|
|
86
|
+
.map((command) => ` ${command}`)
|
|
87
|
+
.join('\n')
|
|
88
|
+
return [
|
|
89
|
+
'[query_index] Could not queue the reindex requested by the migrations that just ran.',
|
|
90
|
+
' Their query-index projections still hold pre-migration values until you run:',
|
|
91
|
+
commands,
|
|
92
|
+
].join('\n')
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type QueryIndexReindexEmitter = {
|
|
96
|
+
emitEvent: (event: string, payload: Record<string, unknown>, options?: { persistent?: boolean }) => Promise<void>
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export type QueryIndexReindexContainer = {
|
|
100
|
+
resolve: (name: string) => unknown
|
|
101
|
+
dispose?: () => Promise<void> | void
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export type RequestQueryIndexReindexDeps = {
|
|
105
|
+
createContainer: () => Promise<QueryIndexReindexContainer>
|
|
106
|
+
onInfo?: (message: string) => void
|
|
107
|
+
onWarn?: (message: string) => void
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Queued rather than executed inline: a rebuild of a large entity type must not hold up a
|
|
112
|
+
* deploy's migration step. `force` stays false so the existing `query_index.reindex`
|
|
113
|
+
* subscriber upserts in place — a forced run purges the entity's rows first, which would
|
|
114
|
+
* leave the projection empty for the duration of the rebuild.
|
|
115
|
+
*/
|
|
116
|
+
export async function requestQueryIndexReindex(
|
|
117
|
+
entityTypes: readonly string[],
|
|
118
|
+
deps: RequestQueryIndexReindexDeps,
|
|
119
|
+
): Promise<{ requested: string[]; queued: boolean }> {
|
|
120
|
+
if (!entityTypes.length) return { requested: [], queued: false }
|
|
121
|
+
|
|
122
|
+
let container: QueryIndexReindexContainer | null = null
|
|
123
|
+
const queued: string[] = []
|
|
124
|
+
try {
|
|
125
|
+
container = await deps.createContainer()
|
|
126
|
+
const eventBus = container.resolve('eventBus') as QueryIndexReindexEmitter
|
|
127
|
+
for (const entityType of entityTypes) {
|
|
128
|
+
await eventBus.emitEvent(
|
|
129
|
+
'query_index.reindex',
|
|
130
|
+
{ entityType, allowAllTenants: true, force: false },
|
|
131
|
+
{ persistent: true },
|
|
132
|
+
)
|
|
133
|
+
queued.push(entityType)
|
|
134
|
+
}
|
|
135
|
+
deps.onInfo?.(
|
|
136
|
+
`[query_index] Queued a reindex for ${queued.length} entity type(s) touched by data migrations: ${queued.join(', ')}`,
|
|
137
|
+
)
|
|
138
|
+
return { requested: queued, queued: true }
|
|
139
|
+
} catch (error) {
|
|
140
|
+
const unqueued = entityTypes.filter((entityType) => !queued.includes(entityType))
|
|
141
|
+
deps.onWarn?.(
|
|
142
|
+
`${formatManualReindexInstructions(unqueued)}\n Reason: ${
|
|
143
|
+
error instanceof Error ? error.message : String(error)
|
|
144
|
+
}`,
|
|
145
|
+
)
|
|
146
|
+
return { requested: queued, queued: false }
|
|
147
|
+
} finally {
|
|
148
|
+
if (container && typeof container.dispose === 'function') {
|
|
149
|
+
try {
|
|
150
|
+
await container.dispose()
|
|
151
|
+
} catch {
|
|
152
|
+
// A container that fails to dispose must not turn a successful migration into a failure.
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|