@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.
Files changed (81) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +2 -0
  3. package/dist/agentic/guides/module-facts.json +128 -128
  4. package/dist/agentic/guides/module-facts.v2.json +128 -128
  5. package/dist/agentic/guides/modules/ai_assistant/index.md +1 -1
  6. package/dist/agentic/guides/modules/api_docs/index.md +1 -1
  7. package/dist/agentic/guides/modules/api_keys/index.md +1 -1
  8. package/dist/agentic/guides/modules/attachments/index.md +1 -1
  9. package/dist/agentic/guides/modules/audit_logs/index.md +1 -1
  10. package/dist/agentic/guides/modules/auth/index.md +1 -1
  11. package/dist/agentic/guides/modules/business_rules/index.md +1 -1
  12. package/dist/agentic/guides/modules/catalog/index.md +1 -1
  13. package/dist/agentic/guides/modules/channel_apns/index.md +1 -1
  14. package/dist/agentic/guides/modules/channel_discord/index.md +1 -1
  15. package/dist/agentic/guides/modules/channel_expo/index.md +1 -1
  16. package/dist/agentic/guides/modules/channel_fcm/index.md +1 -1
  17. package/dist/agentic/guides/modules/channel_gmail/index.md +1 -1
  18. package/dist/agentic/guides/modules/channel_imap/index.md +1 -1
  19. package/dist/agentic/guides/modules/checkout/index.md +1 -1
  20. package/dist/agentic/guides/modules/communication_channels/index.md +1 -1
  21. package/dist/agentic/guides/modules/configs/index.md +1 -1
  22. package/dist/agentic/guides/modules/content/index.md +1 -1
  23. package/dist/agentic/guides/modules/currencies/index.md +1 -1
  24. package/dist/agentic/guides/modules/customer_accounts/index.md +1 -1
  25. package/dist/agentic/guides/modules/customers/index.md +1 -1
  26. package/dist/agentic/guides/modules/dashboards/index.md +1 -1
  27. package/dist/agentic/guides/modules/data_sync/index.md +1 -1
  28. package/dist/agentic/guides/modules/design_system/index.md +1 -1
  29. package/dist/agentic/guides/modules/devices/index.md +1 -1
  30. package/dist/agentic/guides/modules/dictionaries/index.md +1 -1
  31. package/dist/agentic/guides/modules/directory/index.md +1 -1
  32. package/dist/agentic/guides/modules/documents/index.md +1 -1
  33. package/dist/agentic/guides/modules/entities/index.md +1 -1
  34. package/dist/agentic/guides/modules/eudr/index.md +1 -1
  35. package/dist/agentic/guides/modules/events/index.md +1 -1
  36. package/dist/agentic/guides/modules/feature_toggles/index.md +1 -1
  37. package/dist/agentic/guides/modules/gateway_stripe/index.md +1 -1
  38. package/dist/agentic/guides/modules/generators/index.md +1 -1
  39. package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
  40. package/dist/agentic/guides/modules/integrations/index.md +1 -1
  41. package/dist/agentic/guides/modules/messages/index.md +1 -1
  42. package/dist/agentic/guides/modules/notifications/index.md +1 -1
  43. package/dist/agentic/guides/modules/onboarding/index.md +1 -1
  44. package/dist/agentic/guides/modules/payment_gateways/index.md +1 -1
  45. package/dist/agentic/guides/modules/perspectives/index.md +1 -1
  46. package/dist/agentic/guides/modules/planner/index.md +1 -1
  47. package/dist/agentic/guides/modules/portal/index.md +1 -1
  48. package/dist/agentic/guides/modules/progress/index.md +1 -1
  49. package/dist/agentic/guides/modules/push_notifications/index.md +1 -1
  50. package/dist/agentic/guides/modules/query_index/index.md +1 -1
  51. package/dist/agentic/guides/modules/record_locks/index.md +1 -1
  52. package/dist/agentic/guides/modules/resources/index.md +1 -1
  53. package/dist/agentic/guides/modules/sales/index.md +1 -1
  54. package/dist/agentic/guides/modules/scheduler/index.md +1 -1
  55. package/dist/agentic/guides/modules/search/index.md +1 -1
  56. package/dist/agentic/guides/modules/security/index.md +1 -1
  57. package/dist/agentic/guides/modules/shipping_carriers/index.md +1 -1
  58. package/dist/agentic/guides/modules/sso/index.md +1 -1
  59. package/dist/agentic/guides/modules/staff/index.md +1 -1
  60. package/dist/agentic/guides/modules/storage_s3/index.md +1 -1
  61. package/dist/agentic/guides/modules/sync_akeneo/index.md +1 -1
  62. package/dist/agentic/guides/modules/sync_excel/index.md +1 -1
  63. package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
  64. package/dist/agentic/guides/modules/translations/index.md +1 -1
  65. package/dist/agentic/guides/modules/warranty_claims/index.md +1 -1
  66. package/dist/agentic/guides/modules/webhooks/index.md +1 -1
  67. package/dist/agentic/guides/modules/wms/index.md +1 -1
  68. package/dist/agentic/guides/modules/workflows/index.md +1 -1
  69. package/dist/agentic/guides/reference-module-facts.json +1 -1
  70. package/dist/agentic/guides/upstream/manifest.json +1 -1
  71. package/dist/agentic/shared/ai/review-checklist.md +1 -0
  72. package/dist/agentic/shared/ai/skills/om-data-model-design/references/migration-workflow.md +30 -2
  73. package/dist/lib/db/commands.js +106 -60
  74. package/dist/lib/db/commands.js.map +2 -2
  75. package/dist/lib/db/migration-reindex.js +104 -0
  76. package/dist/lib/db/migration-reindex.js.map +7 -0
  77. package/package.json +6 -6
  78. package/src/lib/db/__tests__/commands.migrate-reindex.test.ts +189 -0
  79. package/src/lib/db/__tests__/migration-reindex.test.ts +203 -0
  80. package/src/lib/db/commands.ts +132 -72
  81. 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
+ }