@open-mercato/cli 0.8.1-develop.7297.1.67605e74f9 → 0.9.0
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 +8 -2
- package/AGENTS.md +2 -0
- package/dist/agentic/guides/module-facts.json +7612 -848
- package/dist/agentic/guides/module-facts.v2.json +7612 -848
- package/dist/agentic/guides/modules/agent_orchestrator/active-extension-bindings.md +1 -1
- package/dist/agentic/guides/modules/agent_orchestrator/index.md +1 -1
- package/dist/agentic/guides/modules/ai_assistant/encryption.md +13 -0
- package/dist/agentic/guides/modules/ai_assistant/exact-override-targets.md +3 -0
- package/dist/agentic/guides/modules/ai_assistant/index.md +4 -3
- package/dist/agentic/guides/modules/api_docs/index.md +1 -1
- package/dist/agentic/guides/modules/api_keys/domain-commands.md +12 -0
- package/dist/agentic/guides/modules/api_keys/index.md +3 -2
- 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/availability/acl-features.md +13 -0
- package/dist/agentic/guides/modules/availability/backend-pages.md +14 -0
- package/dist/agentic/guides/modules/availability/di-registrations-rich.md +11 -0
- package/dist/agentic/guides/modules/availability/domain-commands.md +13 -0
- package/dist/agentic/guides/modules/availability/entities.md +11 -0
- package/dist/agentic/guides/modules/availability/events.md +13 -0
- package/dist/agentic/guides/modules/availability/exact-override-targets.md +19 -0
- package/dist/agentic/guides/modules/availability/host-extension-points.md +10 -0
- package/dist/agentic/guides/modules/availability/index.md +22 -0
- package/dist/agentic/guides/modules/availability/owned-contract-module-metadata.md +11 -0
- package/dist/agentic/guides/modules/availability/setup.md +11 -0
- package/dist/agentic/guides/modules/availability/umes-hosts.md +14 -0
- package/dist/agentic/guides/modules/business_rules/index.md +1 -1
- package/dist/agentic/guides/modules/catalog/active-extension-bindings.md +1 -1
- package/dist/agentic/guides/modules/catalog/di-registrations-rich.md +1 -0
- package/dist/agentic/guides/modules/catalog/exact-override-targets.md +3 -0
- package/dist/agentic/guides/modules/catalog/index.md +1 -1
- package/dist/agentic/guides/modules/catalog/workers.md +2 -0
- 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/channel_resend/index.md +1 -1
- package/dist/agentic/guides/modules/channel_ses/index.md +1 -1
- package/dist/agentic/guides/modules/checkout/index.md +1 -1
- package/dist/agentic/guides/modules/communication_channels/acl-features.md +2 -1
- package/dist/agentic/guides/modules/communication_channels/contribution-resolutions.md +1 -0
- package/dist/agentic/guides/modules/communication_channels/di-registrations-rich.md +11 -10
- package/dist/agentic/guides/modules/communication_channels/domain-commands.md +1 -0
- package/dist/agentic/guides/modules/communication_channels/events.md +2 -1
- package/dist/agentic/guides/modules/communication_channels/exact-override-targets.md +12 -10
- package/dist/agentic/guides/modules/communication_channels/incoming-installed-contributions.md +1 -0
- package/dist/agentic/guides/modules/communication_channels/index.md +2 -2
- package/dist/agentic/guides/modules/communication_channels/umes-contributions.md +1 -0
- package/dist/agentic/guides/modules/communication_channels/umes-hosts.md +1 -0
- 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/customer_groups/acl-features.md +16 -0
- package/dist/agentic/guides/modules/customer_groups/active-extension-bindings.md +13 -0
- package/dist/agentic/guides/modules/customer_groups/backend-pages.md +14 -0
- package/dist/agentic/guides/modules/customer_groups/cli-commands.md +11 -0
- package/dist/agentic/guides/modules/customer_groups/contribution-resolutions.md +13 -0
- package/dist/agentic/guides/modules/customer_groups/di-registrations-rich.md +13 -0
- package/dist/agentic/guides/modules/customer_groups/di-service-tokens.md +11 -0
- package/dist/agentic/guides/modules/customer_groups/domain-commands.md +11 -0
- package/dist/agentic/guides/modules/customer_groups/entities.md +13 -0
- package/dist/agentic/guides/modules/customer_groups/events.md +17 -0
- package/dist/agentic/guides/modules/customer_groups/exact-override-targets.md +27 -0
- package/dist/agentic/guides/modules/customer_groups/index.md +26 -0
- package/dist/agentic/guides/modules/customer_groups/owned-contract-module-metadata.md +11 -0
- package/dist/agentic/guides/modules/customer_groups/setup.md +11 -0
- package/dist/agentic/guides/modules/customer_groups/umes-contributions.md +13 -0
- package/dist/agentic/guides/modules/customer_groups/umes-hosts.md +21 -0
- package/dist/agentic/guides/modules/customers/acl-features.md +2 -1
- package/dist/agentic/guides/modules/customers/active-extension-bindings.md +8 -7
- package/dist/agentic/guides/modules/customers/contribution-resolutions.md +1 -0
- package/dist/agentic/guides/modules/customers/domain-commands.md +1 -0
- package/dist/agentic/guides/modules/customers/entities.md +1 -0
- package/dist/agentic/guides/modules/customers/events.md +2 -1
- package/dist/agentic/guides/modules/customers/exact-override-targets.md +2 -0
- package/dist/agentic/guides/modules/customers/index.md +3 -3
- package/dist/agentic/guides/modules/customers/umes-contributions.md +1 -0
- package/dist/agentic/guides/modules/customers/umes-hosts.md +3 -0
- 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/active-extension-bindings.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/active-extension-bindings.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/forms/acl-features.md +16 -0
- package/dist/agentic/guides/modules/forms/active-extension-bindings.md +14 -0
- package/dist/agentic/guides/modules/forms/backend-pages.md +17 -0
- package/dist/agentic/guides/modules/forms/contribution-resolutions.md +21 -0
- package/dist/agentic/guides/modules/forms/di-registrations-rich.md +27 -0
- package/dist/agentic/guides/modules/forms/di-service-tokens.md +19 -0
- package/dist/agentic/guides/modules/forms/domain-commands.md +30 -0
- package/dist/agentic/guides/modules/forms/encryption.md +11 -0
- package/dist/agentic/guides/modules/forms/entities.md +21 -0
- package/dist/agentic/guides/modules/forms/events.md +28 -0
- package/dist/agentic/guides/modules/forms/exact-override-targets.md +59 -0
- package/dist/agentic/guides/modules/forms/frontend-pages.md +16 -0
- package/dist/agentic/guides/modules/forms/index.md +28 -0
- package/dist/agentic/guides/modules/forms/owned-contract-module-metadata.md +11 -0
- package/dist/agentic/guides/modules/forms/setup.md +11 -0
- package/dist/agentic/guides/modules/forms/umes-contributions.md +21 -0
- package/dist/agentic/guides/modules/forms/umes-hosts.md +43 -0
- package/dist/agentic/guides/modules/forms/workers.md +12 -0
- 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/active-extension-bindings.md +1 -1
- package/dist/agentic/guides/modules/inbox_ops/index.md +1 -1
- package/dist/agentic/guides/modules/integrations/di-registrations-rich.md +8 -8
- package/dist/agentic/guides/modules/integrations/di-service-tokens.md +4 -4
- package/dist/agentic/guides/modules/integrations/exact-override-targets.md +8 -8
- 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/phone_calls/index.md +1 -1
- package/dist/agentic/guides/modules/planner/active-extension-bindings.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/active-extension-bindings.md +1 -1
- package/dist/agentic/guides/modules/resources/index.md +1 -1
- package/dist/agentic/guides/modules/sales/active-extension-bindings.md +1 -1
- package/dist/agentic/guides/modules/sales/exact-override-targets.md +4 -4
- package/dist/agentic/guides/modules/sales/index.md +1 -1
- package/dist/agentic/guides/modules/sales/setup.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/seeds/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/active-extension-bindings.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/active-extension-bindings.md +11 -0
- package/dist/agentic/guides/modules/sync_excel/index.md +3 -2
- package/dist/agentic/guides/modules/system_status_overlays/index.md +1 -1
- package/dist/agentic/guides/modules/telemetry/index.md +1 -1
- package/dist/agentic/guides/modules/tillio/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/active-extension-bindings.md +1 -1
- package/dist/agentic/guides/modules/wms/contribution-resolutions.md +3 -0
- package/dist/agentic/guides/modules/wms/di-registrations-rich.md +8 -8
- package/dist/agentic/guides/modules/wms/exact-override-targets.md +11 -8
- package/dist/agentic/guides/modules/wms/index.md +3 -3
- package/dist/agentic/guides/modules/wms/umes-contributions.md +3 -0
- package/dist/agentic/guides/modules/workflows/active-extension-bindings.md +3 -3
- 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/BACKWARD_COMPATIBILITY.md +73 -1
- package/dist/agentic/guides/upstream/manifest.json +2 -2
- package/dist/agentic/shared/ai/skills/om-auto-upgrade-0.8.0-to-0.9.0/SKILL.md +112 -0
- package/dist/agentic/shared/ai/skills/om-data-model-design/references/sensitive-data.md +1 -1
- package/dist/agentic/shared/ai/skills/tiers.json +2 -1
- package/dist/bin.js +2 -2
- package/dist/bin.js.map +2 -2
- package/dist/lib/db/commands.js +84 -5
- package/dist/lib/db/commands.js.map +2 -2
- package/dist/lib/db/encryption-backfill-check.js +112 -0
- package/dist/lib/db/encryption-backfill-check.js.map +7 -0
- package/dist/lib/db/migration-reindex.js +2 -1
- package/dist/lib/db/migration-reindex.js.map +2 -2
- package/dist/lib/events-single-delivery.js.map +2 -2
- package/dist/lib/flush-before-exit.js +19 -0
- package/dist/lib/flush-before-exit.js.map +7 -0
- package/dist/lib/generate-watch-structure.js +113 -19
- package/dist/lib/generate-watch-structure.js.map +3 -3
- package/dist/lib/generators/module-registry.js +4 -2
- package/dist/lib/generators/module-registry.js.map +2 -2
- package/dist/lib/generators/web-research-adapters.js +32 -11
- package/dist/lib/generators/web-research-adapters.js.map +2 -2
- package/dist/lib/telemetry-init.js +69 -11
- package/dist/lib/telemetry-init.js.map +3 -3
- package/dist/lib/testing/integration.js +29 -2
- package/dist/lib/testing/integration.js.map +2 -2
- package/package.json +8 -8
- package/src/bin.ts +4 -4
- package/src/lib/__tests__/flush-before-exit.test.ts +32 -0
- package/src/lib/__tests__/generate-watch-structure.test.ts +261 -3
- package/src/lib/__tests__/telemetry-init.test.ts +89 -0
- package/src/lib/db/__tests__/commands.test.ts +43 -0
- package/src/lib/db/__tests__/dynamic-import-specifier.test.ts +36 -0
- package/src/lib/db/__tests__/encryption-backfill-check.test.ts +146 -0
- package/src/lib/db/__tests__/migration-reindex.test.ts +48 -3
- package/src/lib/db/commands.ts +113 -10
- package/src/lib/db/encryption-backfill-check.ts +163 -0
- package/src/lib/db/migration-reindex.ts +2 -1
- package/src/lib/events-single-delivery.ts +5 -2
- package/src/lib/flush-before-exit.ts +39 -0
- package/src/lib/generate-watch-structure.ts +134 -19
- package/src/lib/generators/__tests__/module-facts.customers.fixture.test.ts +14 -3
- package/src/lib/generators/__tests__/module-package-sources.test.ts +2 -2
- package/src/lib/generators/__tests__/registry-variant-parity.test.ts +16 -0
- package/src/lib/generators/__tests__/web-research-adapters.test.ts +141 -0
- package/src/lib/generators/module-registry.ts +2 -0
- package/src/lib/generators/web-research-adapters.ts +41 -20
- package/src/lib/telemetry-init.ts +68 -11
- package/src/lib/testing/__tests__/integration.test.ts +26 -0
- package/src/lib/testing/integration.ts +32 -2
package/dist/lib/db/commands.js
CHANGED
|
@@ -7,6 +7,11 @@ import { ReflectMetadataProvider } from "@mikro-orm/decorators/legacy";
|
|
|
7
7
|
import { PostgreSqlDriver } from "@mikro-orm/postgresql";
|
|
8
8
|
import { getSslConfig } from "@open-mercato/shared/lib/db/ssl";
|
|
9
9
|
import { quotePostgresIdentifier } from "./identifiers.js";
|
|
10
|
+
import {
|
|
11
|
+
checkEncryptionMapBackfills,
|
|
12
|
+
isEncryptionBackfillCheckEnabled,
|
|
13
|
+
readDeclaredEncryptionMaps
|
|
14
|
+
} from "./encryption-backfill-check.js";
|
|
10
15
|
import {
|
|
11
16
|
collectQueryIndexReindexEntityTypes,
|
|
12
17
|
isMigrationReindexEnabled,
|
|
@@ -50,11 +55,19 @@ function getClientUrl() {
|
|
|
50
55
|
function sortModules(mods) {
|
|
51
56
|
return mods.slice().sort((a, b) => a.id.localeCompare(b.id));
|
|
52
57
|
}
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
-
|
|
58
|
+
function toImportableModuleSpecifier(id) {
|
|
59
|
+
if (id.startsWith("file:") || id.startsWith("data:") || id.startsWith("node:") || id.startsWith("http:") || id.startsWith("https:")) {
|
|
60
|
+
return id;
|
|
56
61
|
}
|
|
57
|
-
|
|
62
|
+
const isWindowsAbsolute = /^[a-zA-Z]:[\\/]/.test(id);
|
|
63
|
+
const isFilesystemRelative = id.startsWith("./") || id.startsWith("../") || id.startsWith(".\\") || id.startsWith("..\\") || /(^|[\\/])node_modules([\\/]|$)/.test(id);
|
|
64
|
+
if (path.isAbsolute(id) || isWindowsAbsolute || isFilesystemRelative) {
|
|
65
|
+
return pathToFileURL(path.resolve(id)).href;
|
|
66
|
+
}
|
|
67
|
+
return id;
|
|
68
|
+
}
|
|
69
|
+
async function dynamicImportProvider(id) {
|
|
70
|
+
return import(toImportableModuleSpecifier(id));
|
|
58
71
|
}
|
|
59
72
|
function sanitizeModuleId(modId) {
|
|
60
73
|
return modId.replace(/[^a-z0-9_]/gi, "_");
|
|
@@ -168,6 +181,69 @@ function getMigrationsPath(entry, resolver) {
|
|
|
168
181
|
}
|
|
169
182
|
return path.join(roots.pkgBase, "migrations").replace(/\\/g, "/");
|
|
170
183
|
}
|
|
184
|
+
async function loadModuleEncryptionMaps(entry, resolver) {
|
|
185
|
+
const roots = resolver.getModulePaths(entry);
|
|
186
|
+
const appFile = path.join(roots.appBase, "encryption.ts");
|
|
187
|
+
const pkgFile = path.join(roots.pkgBase, "encryption.ts");
|
|
188
|
+
try {
|
|
189
|
+
if (fs.existsSync(appFile)) return readDeclaredEncryptionMaps(entry.id, await importWithTypeScriptFile(appFile));
|
|
190
|
+
if (entry.from === "@app" || resolver.isMonorepo()) {
|
|
191
|
+
return fs.existsSync(pkgFile) ? readDeclaredEncryptionMaps(entry.id, await importWithTypeScriptFile(pkgFile)) : [];
|
|
192
|
+
}
|
|
193
|
+
return readDeclaredEncryptionMaps(entry.id, await import(`${resolver.getModuleImportBase(entry).pkgBase}/encryption`));
|
|
194
|
+
} catch {
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function readModuleMigrationSources(modules, resolver) {
|
|
199
|
+
const sources = [];
|
|
200
|
+
for (const entry of modules) {
|
|
201
|
+
const migrationsPath = getMigrationsPath(entry, resolver);
|
|
202
|
+
if (!fs.existsSync(migrationsPath)) continue;
|
|
203
|
+
for (const file of fs.readdirSync(migrationsPath)) {
|
|
204
|
+
if (!/^Migration.*\.(ts|js)$/.test(file) || file.endsWith(".d.ts")) continue;
|
|
205
|
+
sources.push(fs.readFileSync(path.join(migrationsPath, file), "utf8"));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return sources;
|
|
209
|
+
}
|
|
210
|
+
async function queryEncryptionMapRows() {
|
|
211
|
+
const { Client } = await import("pg");
|
|
212
|
+
const client = new Client({ connectionString: getClientUrl(), ssl: getSslConfig(), connectionTimeoutMillis: 5e3 });
|
|
213
|
+
await client.connect();
|
|
214
|
+
try {
|
|
215
|
+
const table = await client.query(`select to_regclass('encryption_maps') is not null as "exists"`);
|
|
216
|
+
if (!table.rows[0]?.exists) return null;
|
|
217
|
+
const result = await client.query(
|
|
218
|
+
`select "tenant_id", "organization_id", "entity_id", "fields_json", "is_active" from "encryption_maps" where "deleted_at" is null`
|
|
219
|
+
);
|
|
220
|
+
return result.rows.map((row) => ({
|
|
221
|
+
tenantId: row.tenant_id ?? null,
|
|
222
|
+
organizationId: row.organization_id ?? null,
|
|
223
|
+
entityId: String(row.entity_id),
|
|
224
|
+
fieldsJson: row.fields_json,
|
|
225
|
+
isActive: row.is_active === true
|
|
226
|
+
}));
|
|
227
|
+
} finally {
|
|
228
|
+
try {
|
|
229
|
+
await client.end();
|
|
230
|
+
} catch {
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async function warnAboutMissingEncryptionMapBackfills(modules, resolver) {
|
|
235
|
+
if (!isEncryptionBackfillCheckEnabled()) return;
|
|
236
|
+
await checkEncryptionMapBackfills({
|
|
237
|
+
loadDeclaredMaps: async () => {
|
|
238
|
+
const declared = [];
|
|
239
|
+
for (const entry of modules) declared.push(...await loadModuleEncryptionMaps(entry, resolver));
|
|
240
|
+
return declared;
|
|
241
|
+
},
|
|
242
|
+
loadMigrationSources: () => readModuleMigrationSources(modules, resolver),
|
|
243
|
+
queryEncryptionMaps: queryEncryptionMapRows,
|
|
244
|
+
warn: (message) => console.warn(message)
|
|
245
|
+
});
|
|
246
|
+
}
|
|
171
247
|
async function dbGenerate(resolver, options = {}) {
|
|
172
248
|
const modules = resolver.loadEnabledModules();
|
|
173
249
|
const ordered = sortModules(modules);
|
|
@@ -256,6 +332,7 @@ async function dbGenerate(resolver, options = {}) {
|
|
|
256
332
|
}
|
|
257
333
|
}
|
|
258
334
|
console.log(results.join("\n"));
|
|
335
|
+
await warnAboutMissingEncryptionMapBackfills(ordered, resolver);
|
|
259
336
|
}
|
|
260
337
|
async function dbMigrate(resolver, options = {}) {
|
|
261
338
|
const modules = resolver.loadEnabledModules();
|
|
@@ -490,6 +567,8 @@ export {
|
|
|
490
567
|
resolveGeneratedMigrationPath,
|
|
491
568
|
sanitizeModuleId,
|
|
492
569
|
shouldCreateInitialModuleMigration,
|
|
493
|
-
|
|
570
|
+
toImportableModuleSpecifier,
|
|
571
|
+
validateTableName,
|
|
572
|
+
warnAboutMissingEncryptionMapBackfills
|
|
494
573
|
};
|
|
495
574
|
//# sourceMappingURL=commands.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/db/commands.ts"],
|
|
4
|
-
"sourcesContent": ["import path from 'node:path'\nimport fs from 'node:fs'\nimport { pathToFileURL } from 'node:url'\nimport ts from 'typescript-js'\nimport { MikroORM, MetadataStorage, type Logger } from '@mikro-orm/core'\nimport { ReflectMetadataProvider } from '@mikro-orm/decorators/legacy'\nimport { Migrator } from '@mikro-orm/migrations'\nimport { PostgreSqlDriver } from '@mikro-orm/postgresql'\nimport { getSslConfig } from '@open-mercato/shared/lib/db/ssl'\nimport type { PackageResolver, ModuleEntry } from '../resolver'\nimport { quotePostgresIdentifier } from './identifiers'\nimport {\n collectQueryIndexReindexEntityTypes,\n isMigrationReindexEnabled,\n requestQueryIndexReindex,\n type AppliedMigration,\n} from './migration-reindex'\n\nconst QUIET_MODE = process.env.OM_CLI_QUIET === '1' || process.env.MERCATO_QUIET === '1'\nconst PROGRESS_EMOJI = ''\n\nfunction formatResult(modId: string, message: string, emoji = '\\u2022') {\n return `${emoji} ${modId}: ${message}`\n}\n\nfunction createProgressRenderer(total: number) {\n const width = 20\n const normalizedTotal = total > 0 ? total : 1\n return (current: number) => {\n const clamped = Math.min(Math.max(current, 0), normalizedTotal)\n const filled = Math.round((clamped / normalizedTotal) * width)\n const bar = `${'='.repeat(filled)}${'.'.repeat(Math.max(width - filled, 0))}`\n return `[${bar}] ${clamped}/${normalizedTotal}`\n }\n}\n\nfunction createMinimalLogger(): Logger {\n return {\n log: () => { },\n error: (_namespace, message) => console.error(message),\n warn: (_namespace, message) => {\n if (!QUIET_MODE) console.warn(message)\n },\n logQuery: () => { },\n setDebugMode: () => { },\n isEnabled: () => false,\n }\n}\n\nfunction getClientUrl(): string {\n const url = process.env.DATABASE_URL\n if (!url) throw new Error('DATABASE_URL is not set')\n return url\n}\n\nfunction sortModules(mods: ModuleEntry[]): ModuleEntry[] {\n // Sort modules alphabetically since they are now isomorphic\n return mods.slice().sort((a, b) => a.id.localeCompare(b.id))\n}\n\n/**\n * Custom dynamic import provider for MikroORM that properly handles Windows paths.\n * MikroORM's built-in handling has a bug where it converts file:// URLs back to\n * Windows paths when the extension isn't in require.extensions (which is always\n * true for .ts files in ESM mode).\n */\nasync function dynamicImportProvider(id: string): Promise<any> {\n // On Windows, convert absolute paths to file:// URLs\n // Check if it's a Windows absolute path (e.g., C:\\... or D:\\...)\n if (process.platform === 'win32' && /^[a-zA-Z]:[\\\\/]/.test(id)) {\n id = pathToFileURL(id).href\n }\n return import(id)\n}\n\n/**\n * Sanitizes a module ID for use in SQL identifiers (table names).\n * Replaces non-alphanumeric characters with underscores to prevent SQL injection.\n * @public Exported for testing\n */\nexport function sanitizeModuleId(modId: string): string {\n return modId.replace(/[^a-z0-9_]/gi, '_')\n}\n\n/**\n * Validates that a table name is safe for use in SQL queries.\n * @throws Error if the table name contains invalid characters.\n * @public Exported for testing\n */\nexport function validateTableName(tableName: string): void {\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) {\n throw new Error(`Invalid table name: ${tableName}. Table names must start with a letter or underscore and contain only alphanumeric characters and underscores.`)\n }\n}\n\nexport function makeConstraintDropsIdempotent(sql: string): string {\n return sql.replace(/alter table\\s+(\"[^\"]+\"|\\S+)\\s+drop constraint\\s+(\"[^\"]+\"|\\S+);/gi, 'alter table $1 drop constraint if exists $2;')\n}\n\nexport function getMigrationSnapshotName(resolver: Pick<PackageResolver, 'getRootDir'>): string {\n void resolver\n return '.snapshot-open-mercato'\n}\n\nexport function shouldCreateInitialModuleMigration(migrationsPath: string, snapshotName: string): boolean {\n const snapshotPath = path.join(migrationsPath, `${snapshotName}.json`)\n if (fs.existsSync(snapshotPath)) return false\n if (!fs.existsSync(migrationsPath)) return true\n\n const migrationFiles = fs\n .readdirSync(migrationsPath)\n .filter((file) => /^Migration.*\\.(ts|js)$/.test(file) && !file.endsWith('.d.ts'))\n\n return migrationFiles.length === 0\n}\n\nexport function resolveGeneratedMigrationPath(fileName: string, migrationsPath: string): string {\n return path.isAbsolute(fileName) ? fileName : path.join(migrationsPath, fileName)\n}\n\nlet tsxLoaderRegistered = false\nlet temporaryModuleCounter = 0\n\nasync function ensureTsxLoaderRegistered() {\n if (tsxLoaderRegistered) return\n try {\n const { register } = await import('tsx/esm/api')\n register()\n tsxLoaderRegistered = true\n } catch {\n // Continue without the loader. Relative TypeScript imports may fail in this case.\n }\n}\n\nasync function importWithTypeScriptFile(filePath: string): Promise<any> {\n await ensureTsxLoaderRegistered()\n const source = fs.readFileSync(filePath, 'utf8')\n const compiled = ts.transpileModule(source, {\n fileName: filePath,\n compilerOptions: {\n module: ts.ModuleKind.ESNext,\n target: ts.ScriptTarget.ES2022,\n moduleResolution: ts.ModuleResolutionKind.Bundler,\n esModuleInterop: true,\n resolveJsonModule: true,\n jsx: ts.JsxEmit.ReactJSX,\n experimentalDecorators: true,\n emitDecoratorMetadata: false,\n useDefineForClassFields: false,\n },\n }).outputText\n const tempPath = `${filePath}.mercato-db-generate-${process.pid}-${temporaryModuleCounter++}.mjs`\n fs.writeFileSync(tempPath, compiled, 'utf8')\n try {\n return await import(pathToFileURL(tempPath).href)\n } finally {\n try {\n fs.unlinkSync(tempPath)\n } catch {\n // Ignore cleanup failures for temporary compiled modules.\n }\n }\n}\n\nasync function loadModuleEntities(entry: ModuleEntry, resolver: PackageResolver): Promise<any[]> {\n const roots = resolver.getModulePaths(entry)\n const imps = resolver.getModuleImportBase(entry)\n const isAppModule = entry.from === '@app'\n const shouldImportFromSource = isAppModule || resolver.isMonorepo()\n const bases = [\n path.join(roots.appBase, 'data'),\n path.join(roots.pkgBase, 'data'),\n path.join(roots.appBase, 'db'),\n path.join(roots.pkgBase, 'db'),\n ]\n const candidates = ['entities.ts', 'schema.ts']\n\n for (const base of bases) {\n for (const f of candidates) {\n const p = path.join(base, f)\n if (fs.existsSync(p)) {\n const sub = path.basename(base)\n const fromApp = base.startsWith(roots.appBase)\n const importPath = fromApp || shouldImportFromSource\n ? pathToFileURL(p).href\n : `${imps.pkgBase}/${sub}/${f.replace(/\\.ts$/, '')}`\n try {\n const mod = fromApp || shouldImportFromSource\n ? await importWithTypeScriptFile(p)\n : await import(importPath)\n const entities = Object.values(mod).filter((v) => typeof v === 'function')\n if (entities.length) return entities as any[]\n } catch (err) {\n // For @app modules we try a TS loader fallback; otherwise propagate errors\n if (isAppModule) {\n if (process.env.MERCATO_CLI_DEBUG_IMPORTS === '1') {\n console.warn(`[db] failed to load app module entities from ${p}: ${(err as Error)?.message || String(err)}`)\n }\n continue\n }\n throw err\n }\n }\n }\n }\n return []\n}\n\nfunction getMigrationsPath(entry: ModuleEntry, resolver: PackageResolver): string {\n const roots = resolver.getModulePaths(entry)\n\n if (entry.from === '@app') {\n // @app modules: use src/ (user's TypeScript source)\n // Normalize to forward slashes for ESM compatibility on Windows\n return path.join(roots.appBase, 'migrations').replace(/\\\\/g, '/')\n }\n\n // Package modules: in standalone mode, use dist/ (compiled JS) since Node.js\n // can't run TypeScript from node_modules. In monorepo, use src/ (TypeScript).\n if (!resolver.isMonorepo()) {\n // Replace src/modules with dist/modules for standalone apps\n // Use regex to handle both forward and backslashes\n const distPath = roots.pkgBase.replace(/[/\\\\]src[/\\\\]modules[/\\\\]/, '/dist/modules/')\n return path.join(distPath, 'migrations').replace(/\\\\/g, '/')\n }\n\n // Normalize to forward slashes for ESM compatibility on Windows\n return path.join(roots.pkgBase, 'migrations').replace(/\\\\/g, '/')\n}\n\nexport interface DbOptions {\n quiet?: boolean\n}\n\nexport interface GreenfieldOptions extends DbOptions {\n yes: boolean\n}\n\nexport async function dbGenerate(resolver: PackageResolver, options: DbOptions = {}): Promise<void> {\n const modules = resolver.loadEnabledModules()\n const ordered = sortModules(modules)\n const results: string[] = []\n\n const sslConfig = getSslConfig()\n const usedFileNames = new Set<string>()\n\n for (const entry of ordered) {\n // Clear the global @Entity() decorator registry before loading this module's\n // entities. MikroORM's migrator reads MetadataStorage at createMigration()\n // time, so without this clear, every module's migration would include every\n // previously-loaded module's tables (see issue #1911).\n MetadataStorage.clear()\n\n const modId = entry.id\n const sanitizedModId = sanitizeModuleId(modId)\n const entities = await loadModuleEntities(entry, resolver)\n if (!entities.length) {\n if (entry.from === '@app') {\n results.push(formatResult(modId, 'no entities discovered', ''))\n }\n continue\n }\n\n const migrationsPath = getMigrationsPath(entry, resolver)\n fs.mkdirSync(migrationsPath, { recursive: true })\n\n const tableName = `mikro_orm_migrations_${sanitizedModId}`\n validateTableName(tableName)\n const snapshotName = getMigrationSnapshotName(resolver)\n const createInitialMigration = shouldCreateInitialModuleMigration(migrationsPath, snapshotName)\n\n const orm = await MikroORM.init<PostgreSqlDriver>({\n driver: PostgreSqlDriver,\n clientUrl: getClientUrl(),\n loggerFactory: () => createMinimalLogger(),\n dynamicImportProvider,\n entities,\n metadataProvider: ReflectMetadataProvider,\n migrations: {\n path: migrationsPath,\n glob: '!(*.d).{ts,js}',\n tableName,\n snapshotName,\n dropTables: false,\n },\n schemaGenerator: {\n disableForeignKeys: true,\n },\n pool: {\n min: 1,\n max: 3,\n idleTimeoutMillis: 30000,\n // acquireTimeoutMillis removed for v7 (pg.Pool doesn't support it; use connectionTimeoutMillis in driverOptions if needed)\n },\n driverOptions: sslConfig ? {\n ssl: sslConfig,\n } : undefined,\n })\n\n try {\n const diff = await orm.migrator.create(undefined, false, createInitialMigration)\n if (diff && diff.fileName) {\n try {\n const orig = resolveGeneratedMigrationPath(diff.fileName, migrationsPath)\n const base = path.basename(orig)\n const dir = path.dirname(orig)\n const ext = path.extname(base)\n const stem = base.replace(ext, '')\n const suffix = `_${modId}`\n let candidate = stem.endsWith(suffix) ? base : `${stem}${suffix}${ext}`\n let dedupe = 1\n\n while (usedFileNames.has(path.join(dir, candidate))) {\n candidate = `${stem}${suffix}_${dedupe++}${ext}`\n }\n\n const newPath = path.join(dir, candidate)\n let content = fs.readFileSync(orig, 'utf8')\n content = makeConstraintDropsIdempotent(content)\n content = content.replace(\n /export class (Migration\\d+)/,\n `export class $1_${modId.replace(/[^a-zA-Z0-9]/g, '_')}`\n )\n fs.writeFileSync(newPath, content, 'utf8')\n if (newPath !== orig) fs.unlinkSync(orig)\n usedFileNames.add(newPath)\n results.push(formatResult(modId, `generated ${path.basename(newPath)}`, ''))\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n results.push(formatResult(modId, `generated ${path.basename(diff.fileName)} (rename failed: ${message})`, ''))\n }\n } else {\n results.push(formatResult(modId, 'no changes', ''))\n }\n } finally {\n await orm.close(true)\n }\n }\n\n console.log(results.join('\\n'))\n}\n\nexport async function dbMigrate(resolver: PackageResolver, options: DbOptions = {}): Promise<void> {\n const modules = resolver.loadEnabledModules()\n const ordered = sortModules(modules)\n const results: string[] = []\n const appliedMigrations: AppliedMigration[] = []\n\n try {\n for (const entry of ordered) {\n const modId = entry.id\n const sanitizedModId = sanitizeModuleId(modId)\n const entities = await loadModuleEntities(entry, resolver)\n\n const migrationsPath = getMigrationsPath(entry, resolver)\n\n // Skip if no entities AND no migrations directory exists\n // (allows @app modules to run migrations even if entities can't be dynamically imported)\n if (!entities.length && !fs.existsSync(migrationsPath)) continue\n fs.mkdirSync(migrationsPath, { recursive: true })\n\n const tableName = `mikro_orm_migrations_${sanitizedModId}`\n validateTableName(tableName)\n\n // dbMigrate only runs existing migration files \u2014 entities are intentionally\n // omitted so MikroORM does not compare them against the snapshot and\n // auto-generate a phantom diff migration (that would duplicate tables\n // already created by committed migrations).\n const sslConfig = getSslConfig()\n const orm = await MikroORM.init<PostgreSqlDriver>({\n driver: PostgreSqlDriver,\n clientUrl: getClientUrl(),\n loggerFactory: () => createMinimalLogger(),\n dynamicImportProvider,\n entities: [],\n metadataProvider: ReflectMetadataProvider,\n discovery: { warnWhenNoEntities: false },\n migrations: {\n path: migrationsPath,\n glob: '!(*.d).{ts,js}',\n tableName,\n snapshot: false,\n dropTables: false,\n },\n schemaGenerator: {\n disableForeignKeys: true,\n },\n pool: {\n min: 1,\n max: 3,\n idleTimeoutMillis: 30000,\n // acquireTimeoutMillis removed for v7 (pg.Pool doesn't support it; use connectionTimeoutMillis in driverOptions if needed)\n },\n driverOptions: sslConfig ? {\n ssl: sslConfig,\n } : undefined,\n })\n\n try {\n const migrator = orm.migrator as Migrator\n const pending = await migrator.getPending()\n if (!pending.length) {\n results.push(formatResult(modId, 'no pending migrations', ''))\n } else {\n const renderProgress = createProgressRenderer(pending.length)\n let applied = 0\n if (!QUIET_MODE) {\n process.stdout.write(` ${PROGRESS_EMOJI} ${modId}: ${renderProgress(applied)}`)\n }\n for (const migration of pending) {\n const migrationName =\n typeof migration === 'string'\n ? migration\n : (migration as any).name ?? (migration as any).fileName\n await migrator.up(migrationName ? { migrations: [migrationName] } : undefined)\n if (migrationName) {\n appliedMigrations.push({\n moduleId: modId,\n migrationsPath,\n name: String(migrationName),\n filePath: typeof migration === 'string' ? null : (migration as any).path ?? null,\n })\n }\n applied += 1\n if (!QUIET_MODE) {\n process.stdout.write(`\\r ${PROGRESS_EMOJI} ${modId}: ${renderProgress(applied)}`)\n }\n }\n if (!QUIET_MODE) process.stdout.write('\\n')\n results.push(\n formatResult(modId, `${pending.length} migration${pending.length === 1 ? '' : 's'} applied`, '')\n )\n }\n } finally {\n await orm.close(true)\n }\n }\n } finally {\n console.log(results.join('\\n'))\n await dischargeQueryIndexReindexRequests(appliedMigrations)\n }\n}\n\n/**\n * Runs after every module migrated and after `orm.close()`, so the rewrites the declarations\n * refer to are committed before the reindex is queued.\n *\n * Reached from a `finally`, so it also runs when a later migration threw: the migrations that\n * did apply already committed, and their declarations are the only record that their projections\n * are stale \u2014 a re-run cannot recover them, because `getPending()` no longer lists them. It must\n * therefore never throw, or it would replace the migration failure the operator needs to see.\n */\nexport async function dischargeQueryIndexReindexRequests(\n applied: readonly AppliedMigration[],\n): Promise<void> {\n if (!applied.length || !isMigrationReindexEnabled()) return\n\n try {\n const entityTypes = await collectQueryIndexReindexEntityTypes(applied, {\n importModule: dynamicImportProvider,\n onWarn: (message) => console.warn(message),\n })\n if (!entityTypes.length) return\n\n await requestQueryIndexReindex(entityTypes, {\n createContainer: async () => {\n const { createRequestContainer } = await import('@open-mercato/shared/lib/di/container')\n return (await createRequestContainer()) as any\n },\n onInfo: (message) => {\n if (!QUIET_MODE) console.log(message)\n },\n onWarn: (message) => console.warn(message),\n })\n } catch (error) {\n console.warn(\n `[query_index] Could not determine which projections the migrations invalidated: ${\n error instanceof Error ? error.message : String(error)\n }`,\n )\n }\n}\n\nexport async function dbGreenfield(resolver: PackageResolver, options: GreenfieldOptions): Promise<void> {\n if (!options.yes) {\n console.error('This command will DELETE all data. Use --yes to confirm.')\n process.exit(1)\n }\n\n console.log('Cleaning up migrations and snapshots for greenfield setup...')\n\n const modules = resolver.loadEnabledModules()\n const ordered = sortModules(modules)\n const results: string[] = []\n const outputDir = resolver.getOutputDir()\n\n for (const entry of ordered) {\n const modId = entry.id\n const migrationsPath = getMigrationsPath(entry, resolver)\n\n if (fs.existsSync(migrationsPath)) {\n // Remove all migration files\n const migrationFiles = fs\n .readdirSync(migrationsPath)\n .filter((file) => file.endsWith('.ts') && file.startsWith('Migration'))\n\n for (const file of migrationFiles) {\n fs.unlinkSync(path.join(migrationsPath, file))\n }\n\n // Remove snapshot files\n const snapshotFiles = fs\n .readdirSync(migrationsPath)\n .filter((file) => file.endsWith('.json') && file.includes('snapshot'))\n\n for (const file of snapshotFiles) {\n fs.unlinkSync(path.join(migrationsPath, file))\n }\n\n if (migrationFiles.length > 0 || snapshotFiles.length > 0) {\n results.push(\n formatResult(modId, `cleaned ${migrationFiles.length} migrations, ${snapshotFiles.length} snapshots`, '')\n )\n } else {\n results.push(formatResult(modId, 'already clean', ''))\n }\n } else {\n results.push(formatResult(modId, 'no migrations directory', ''))\n }\n\n // Clean up checksum files using glob pattern\n if (fs.existsSync(outputDir)) {\n const files = fs.readdirSync(outputDir)\n const checksumFiles = files.filter((file) => file.endsWith('.checksum'))\n\n for (const file of checksumFiles) {\n fs.unlinkSync(path.join(outputDir, file))\n }\n\n if (checksumFiles.length > 0) {\n results.push(formatResult(modId, `cleaned ${checksumFiles.length} checksum files`, ''))\n }\n }\n }\n\n console.log(results.join('\\n'))\n\n // Drop per-module MikroORM migration tables to ensure clean slate\n console.log('Dropping per-module migration tables...')\n try {\n const { Client } = await import('pg')\n const client = new Client({ connectionString: getClientUrl(), ssl: getSslConfig() })\n await client.connect()\n try {\n await client.query('BEGIN')\n for (const entry of ordered) {\n const modId = entry.id\n const sanitizedModId = sanitizeModuleId(modId)\n const tableName = `mikro_orm_migrations_${sanitizedModId}`\n validateTableName(tableName)\n await client.query(`DROP TABLE IF EXISTS \"${tableName}\"`)\n console.log(` ${modId}: dropped table ${tableName}`)\n }\n await client.query('COMMIT')\n } catch (e) {\n await client.query('ROLLBACK')\n throw e\n } finally {\n try {\n await client.end()\n } catch { }\n }\n } catch (e) {\n console.error('Failed to drop migration tables:', (e as any)?.message || e)\n throw e\n }\n\n // Drop all existing user tables to ensure fresh CREATE-only migrations\n console.log('Dropping ALL public tables for true greenfield...')\n try {\n const { Client } = await import('pg')\n const client = new Client({ connectionString: getClientUrl(), ssl: getSslConfig() })\n await client.connect()\n try {\n const res = await client.query(`SELECT tablename FROM pg_tables WHERE schemaname = current_schema()`)\n const tables: string[] = (res.rows || []).map((r: any) => String(r.tablename))\n if (tables.length) {\n await client.query('BEGIN')\n try {\n await client.query(\"SET session_replication_role = 'replica'\")\n for (const t of tables) {\n await client.query(`DROP TABLE IF EXISTS ${quotePostgresIdentifier(t)} CASCADE`)\n }\n await client.query(\"SET session_replication_role = 'origin'\")\n await client.query('COMMIT')\n console.log(` Dropped ${tables.length} tables.`)\n } catch (e) {\n await client.query('ROLLBACK')\n throw e\n }\n } else {\n console.log(' No tables found to drop.')\n }\n } finally {\n try {\n await client.end()\n } catch { }\n }\n } catch (e) {\n console.error('Failed to drop public tables:', (e as any)?.message || e)\n throw e\n }\n\n // Generate fresh migrations for all modules\n console.log('Generating fresh migrations for all modules...')\n await dbGenerate(resolver)\n\n // Apply migrations\n console.log('Applying migrations...')\n await dbMigrate(resolver)\n\n console.log('Greenfield reset complete! Fresh migrations generated and applied.')\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import path from 'node:path'\nimport fs from 'node:fs'\nimport { pathToFileURL } from 'node:url'\nimport ts from 'typescript-js'\nimport { MikroORM, MetadataStorage, type Logger } from '@mikro-orm/core'\nimport { ReflectMetadataProvider } from '@mikro-orm/decorators/legacy'\nimport { Migrator } from '@mikro-orm/migrations'\nimport { PostgreSqlDriver } from '@mikro-orm/postgresql'\nimport { getSslConfig } from '@open-mercato/shared/lib/db/ssl'\nimport type { PackageResolver, ModuleEntry } from '../resolver'\nimport { quotePostgresIdentifier } from './identifiers'\nimport {\n checkEncryptionMapBackfills,\n isEncryptionBackfillCheckEnabled,\n readDeclaredEncryptionMaps,\n type DeclaredEncryptionMap,\n type EncryptionMapRow,\n} from './encryption-backfill-check'\nimport {\n collectQueryIndexReindexEntityTypes,\n isMigrationReindexEnabled,\n requestQueryIndexReindex,\n type AppliedMigration,\n} from './migration-reindex'\n\nconst QUIET_MODE = process.env.OM_CLI_QUIET === '1' || process.env.MERCATO_QUIET === '1'\nconst PROGRESS_EMOJI = ''\n\nfunction formatResult(modId: string, message: string, emoji = '\\u2022') {\n return `${emoji} ${modId}: ${message}`\n}\n\nfunction createProgressRenderer(total: number) {\n const width = 20\n const normalizedTotal = total > 0 ? total : 1\n return (current: number) => {\n const clamped = Math.min(Math.max(current, 0), normalizedTotal)\n const filled = Math.round((clamped / normalizedTotal) * width)\n const bar = `${'='.repeat(filled)}${'.'.repeat(Math.max(width - filled, 0))}`\n return `[${bar}] ${clamped}/${normalizedTotal}`\n }\n}\n\nfunction createMinimalLogger(): Logger {\n return {\n log: () => { },\n error: (_namespace, message) => console.error(message),\n warn: (_namespace, message) => {\n if (!QUIET_MODE) console.warn(message)\n },\n logQuery: () => { },\n setDebugMode: () => { },\n isEnabled: () => false,\n }\n}\n\nfunction getClientUrl(): string {\n const url = process.env.DATABASE_URL\n if (!url) throw new Error('DATABASE_URL is not set')\n return url\n}\n\nfunction sortModules(mods: ModuleEntry[]): ModuleEntry[] {\n // Sort modules alphabetically since they are now isomorphic\n return mods.slice().sort((a, b) => a.id.localeCompare(b.id))\n}\n\n/**\n * Convert a filesystem path into a specifier `import()` can resolve under plain\n * Node ESM on every platform (#6238).\n *\n * Windows already needed `file:` URLs for drive-letter absolute paths. On POSIX,\n * cwd-relative paths such as `node_modules/.../Migration.js` are treated as\n * bare package names (`node_modules`), so migration reindex declarations were\n * logged and skipped. Absolute paths become `file:` URLs on every platform.\n * Already-URL / `node:` / bare package (and package-subpath) specifiers stay as-is.\n */\nexport function toImportableModuleSpecifier(id: string): string {\n if (\n id.startsWith('file:') ||\n id.startsWith('data:') ||\n id.startsWith('node:') ||\n id.startsWith('http:') ||\n id.startsWith('https:')\n ) {\n return id\n }\n\n const isWindowsAbsolute = /^[a-zA-Z]:[\\\\/]/.test(id)\n const isFilesystemRelative =\n id.startsWith('./') ||\n id.startsWith('../') ||\n id.startsWith('.\\\\') ||\n id.startsWith('..\\\\') ||\n /(^|[\\\\/])node_modules([\\\\/]|$)/.test(id)\n\n if (path.isAbsolute(id) || isWindowsAbsolute || isFilesystemRelative) {\n return pathToFileURL(path.resolve(id)).href\n }\n\n return id\n}\n\n/**\n * Custom dynamic import provider for MikroORM and migration reindex loading.\n * Always converts filesystem paths to `file:` URLs before `import()` (#6238).\n */\nasync function dynamicImportProvider(id: string): Promise<any> {\n return import(toImportableModuleSpecifier(id))\n}\n\n/**\n * Sanitizes a module ID for use in SQL identifiers (table names).\n * Replaces non-alphanumeric characters with underscores to prevent SQL injection.\n * @public Exported for testing\n */\nexport function sanitizeModuleId(modId: string): string {\n return modId.replace(/[^a-z0-9_]/gi, '_')\n}\n\n/**\n * Validates that a table name is safe for use in SQL queries.\n * @throws Error if the table name contains invalid characters.\n * @public Exported for testing\n */\nexport function validateTableName(tableName: string): void {\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tableName)) {\n throw new Error(`Invalid table name: ${tableName}. Table names must start with a letter or underscore and contain only alphanumeric characters and underscores.`)\n }\n}\n\nexport function makeConstraintDropsIdempotent(sql: string): string {\n return sql.replace(/alter table\\s+(\"[^\"]+\"|\\S+)\\s+drop constraint\\s+(\"[^\"]+\"|\\S+);/gi, 'alter table $1 drop constraint if exists $2;')\n}\n\nexport function getMigrationSnapshotName(resolver: Pick<PackageResolver, 'getRootDir'>): string {\n void resolver\n return '.snapshot-open-mercato'\n}\n\nexport function shouldCreateInitialModuleMigration(migrationsPath: string, snapshotName: string): boolean {\n const snapshotPath = path.join(migrationsPath, `${snapshotName}.json`)\n if (fs.existsSync(snapshotPath)) return false\n if (!fs.existsSync(migrationsPath)) return true\n\n const migrationFiles = fs\n .readdirSync(migrationsPath)\n .filter((file) => /^Migration.*\\.(ts|js)$/.test(file) && !file.endsWith('.d.ts'))\n\n return migrationFiles.length === 0\n}\n\nexport function resolveGeneratedMigrationPath(fileName: string, migrationsPath: string): string {\n return path.isAbsolute(fileName) ? fileName : path.join(migrationsPath, fileName)\n}\n\nlet tsxLoaderRegistered = false\nlet temporaryModuleCounter = 0\n\nasync function ensureTsxLoaderRegistered() {\n if (tsxLoaderRegistered) return\n try {\n const { register } = await import('tsx/esm/api')\n register()\n tsxLoaderRegistered = true\n } catch {\n // Continue without the loader. Relative TypeScript imports may fail in this case.\n }\n}\n\nasync function importWithTypeScriptFile(filePath: string): Promise<any> {\n await ensureTsxLoaderRegistered()\n const source = fs.readFileSync(filePath, 'utf8')\n const compiled = ts.transpileModule(source, {\n fileName: filePath,\n compilerOptions: {\n module: ts.ModuleKind.ESNext,\n target: ts.ScriptTarget.ES2022,\n moduleResolution: ts.ModuleResolutionKind.Bundler,\n esModuleInterop: true,\n resolveJsonModule: true,\n jsx: ts.JsxEmit.ReactJSX,\n experimentalDecorators: true,\n emitDecoratorMetadata: false,\n useDefineForClassFields: false,\n },\n }).outputText\n const tempPath = `${filePath}.mercato-db-generate-${process.pid}-${temporaryModuleCounter++}.mjs`\n fs.writeFileSync(tempPath, compiled, 'utf8')\n try {\n return await import(pathToFileURL(tempPath).href)\n } finally {\n try {\n fs.unlinkSync(tempPath)\n } catch {\n // Ignore cleanup failures for temporary compiled modules.\n }\n }\n}\n\nasync function loadModuleEntities(entry: ModuleEntry, resolver: PackageResolver): Promise<any[]> {\n const roots = resolver.getModulePaths(entry)\n const imps = resolver.getModuleImportBase(entry)\n const isAppModule = entry.from === '@app'\n const shouldImportFromSource = isAppModule || resolver.isMonorepo()\n const bases = [\n path.join(roots.appBase, 'data'),\n path.join(roots.pkgBase, 'data'),\n path.join(roots.appBase, 'db'),\n path.join(roots.pkgBase, 'db'),\n ]\n const candidates = ['entities.ts', 'schema.ts']\n\n for (const base of bases) {\n for (const f of candidates) {\n const p = path.join(base, f)\n if (fs.existsSync(p)) {\n const sub = path.basename(base)\n const fromApp = base.startsWith(roots.appBase)\n const importPath = fromApp || shouldImportFromSource\n ? pathToFileURL(p).href\n : `${imps.pkgBase}/${sub}/${f.replace(/\\.ts$/, '')}`\n try {\n const mod = fromApp || shouldImportFromSource\n ? await importWithTypeScriptFile(p)\n : await import(importPath)\n const entities = Object.values(mod).filter((v) => typeof v === 'function')\n if (entities.length) return entities as any[]\n } catch (err) {\n // For @app modules we try a TS loader fallback; otherwise propagate errors\n if (isAppModule) {\n if (process.env.MERCATO_CLI_DEBUG_IMPORTS === '1') {\n console.warn(`[db] failed to load app module entities from ${p}: ${(err as Error)?.message || String(err)}`)\n }\n continue\n }\n throw err\n }\n }\n }\n }\n return []\n}\n\nfunction getMigrationsPath(entry: ModuleEntry, resolver: PackageResolver): string {\n const roots = resolver.getModulePaths(entry)\n\n if (entry.from === '@app') {\n // @app modules: use src/ (user's TypeScript source)\n // Normalize to forward slashes for ESM compatibility on Windows\n return path.join(roots.appBase, 'migrations').replace(/\\\\/g, '/')\n }\n\n // Package modules: in standalone mode, use dist/ (compiled JS) since Node.js\n // can't run TypeScript from node_modules. In monorepo, use src/ (TypeScript).\n if (!resolver.isMonorepo()) {\n // Replace src/modules with dist/modules for standalone apps\n // Use regex to handle both forward and backslashes\n const distPath = roots.pkgBase.replace(/[/\\\\]src[/\\\\]modules[/\\\\]/, '/dist/modules/')\n return path.join(distPath, 'migrations').replace(/\\\\/g, '/')\n }\n\n // Normalize to forward slashes for ESM compatibility on Windows\n return path.join(roots.pkgBase, 'migrations').replace(/\\\\/g, '/')\n}\n\nasync function loadModuleEncryptionMaps(entry: ModuleEntry, resolver: PackageResolver): Promise<DeclaredEncryptionMap[]> {\n const roots = resolver.getModulePaths(entry)\n const appFile = path.join(roots.appBase, 'encryption.ts')\n const pkgFile = path.join(roots.pkgBase, 'encryption.ts')\n try {\n if (fs.existsSync(appFile)) return readDeclaredEncryptionMaps(entry.id, await importWithTypeScriptFile(appFile))\n if (entry.from === '@app' || resolver.isMonorepo()) {\n return fs.existsSync(pkgFile) ? readDeclaredEncryptionMaps(entry.id, await importWithTypeScriptFile(pkgFile)) : []\n }\n return readDeclaredEncryptionMaps(entry.id, await import(`${resolver.getModuleImportBase(entry).pkgBase}/encryption`))\n } catch {\n return []\n }\n}\n\nfunction readModuleMigrationSources(modules: ModuleEntry[], resolver: PackageResolver): string[] {\n const sources: string[] = []\n for (const entry of modules) {\n const migrationsPath = getMigrationsPath(entry, resolver)\n if (!fs.existsSync(migrationsPath)) continue\n for (const file of fs.readdirSync(migrationsPath)) {\n if (!/^Migration.*\\.(ts|js)$/.test(file) || file.endsWith('.d.ts')) continue\n sources.push(fs.readFileSync(path.join(migrationsPath, file), 'utf8'))\n }\n }\n return sources\n}\n\nasync function queryEncryptionMapRows(): Promise<EncryptionMapRow[] | null> {\n const { Client } = await import('pg')\n const client = new Client({ connectionString: getClientUrl(), ssl: getSslConfig(), connectionTimeoutMillis: 5000 })\n await client.connect()\n try {\n const table = await client.query(`select to_regclass('encryption_maps') is not null as \"exists\"`)\n if (!table.rows[0]?.exists) return null\n const result = await client.query(\n `select \"tenant_id\", \"organization_id\", \"entity_id\", \"fields_json\", \"is_active\" from \"encryption_maps\" where \"deleted_at\" is null`,\n )\n return result.rows.map((row) => ({\n tenantId: row.tenant_id ?? null,\n organizationId: row.organization_id ?? null,\n entityId: String(row.entity_id),\n fieldsJson: row.fields_json,\n isActive: row.is_active === true,\n }))\n } finally {\n try {\n await client.end()\n } catch { }\n }\n}\n\nexport async function warnAboutMissingEncryptionMapBackfills(modules: ModuleEntry[], resolver: PackageResolver): Promise<void> {\n if (!isEncryptionBackfillCheckEnabled()) return\n await checkEncryptionMapBackfills({\n loadDeclaredMaps: async () => {\n const declared: DeclaredEncryptionMap[] = []\n for (const entry of modules) declared.push(...(await loadModuleEncryptionMaps(entry, resolver)))\n return declared\n },\n loadMigrationSources: () => readModuleMigrationSources(modules, resolver),\n queryEncryptionMaps: queryEncryptionMapRows,\n warn: (message) => console.warn(message),\n })\n}\n\nexport interface DbOptions {\n quiet?: boolean\n}\n\nexport interface GreenfieldOptions extends DbOptions {\n yes: boolean\n}\n\nexport async function dbGenerate(resolver: PackageResolver, options: DbOptions = {}): Promise<void> {\n const modules = resolver.loadEnabledModules()\n const ordered = sortModules(modules)\n const results: string[] = []\n\n const sslConfig = getSslConfig()\n const usedFileNames = new Set<string>()\n\n for (const entry of ordered) {\n // Clear the global @Entity() decorator registry before loading this module's\n // entities. MikroORM's migrator reads MetadataStorage at createMigration()\n // time, so without this clear, every module's migration would include every\n // previously-loaded module's tables (see issue #1911).\n MetadataStorage.clear()\n\n const modId = entry.id\n const sanitizedModId = sanitizeModuleId(modId)\n const entities = await loadModuleEntities(entry, resolver)\n if (!entities.length) {\n if (entry.from === '@app') {\n results.push(formatResult(modId, 'no entities discovered', ''))\n }\n continue\n }\n\n const migrationsPath = getMigrationsPath(entry, resolver)\n fs.mkdirSync(migrationsPath, { recursive: true })\n\n const tableName = `mikro_orm_migrations_${sanitizedModId}`\n validateTableName(tableName)\n const snapshotName = getMigrationSnapshotName(resolver)\n const createInitialMigration = shouldCreateInitialModuleMigration(migrationsPath, snapshotName)\n\n const orm = await MikroORM.init<PostgreSqlDriver>({\n driver: PostgreSqlDriver,\n clientUrl: getClientUrl(),\n loggerFactory: () => createMinimalLogger(),\n dynamicImportProvider,\n entities,\n metadataProvider: ReflectMetadataProvider,\n migrations: {\n path: migrationsPath,\n glob: '!(*.d).{ts,js}',\n tableName,\n snapshotName,\n dropTables: false,\n },\n schemaGenerator: {\n disableForeignKeys: true,\n },\n pool: {\n min: 1,\n max: 3,\n idleTimeoutMillis: 30000,\n // acquireTimeoutMillis removed for v7 (pg.Pool doesn't support it; use connectionTimeoutMillis in driverOptions if needed)\n },\n driverOptions: sslConfig ? {\n ssl: sslConfig,\n } : undefined,\n })\n\n try {\n const diff = await orm.migrator.create(undefined, false, createInitialMigration)\n if (diff && diff.fileName) {\n try {\n const orig = resolveGeneratedMigrationPath(diff.fileName, migrationsPath)\n const base = path.basename(orig)\n const dir = path.dirname(orig)\n const ext = path.extname(base)\n const stem = base.replace(ext, '')\n const suffix = `_${modId}`\n let candidate = stem.endsWith(suffix) ? base : `${stem}${suffix}${ext}`\n let dedupe = 1\n\n while (usedFileNames.has(path.join(dir, candidate))) {\n candidate = `${stem}${suffix}_${dedupe++}${ext}`\n }\n\n const newPath = path.join(dir, candidate)\n let content = fs.readFileSync(orig, 'utf8')\n content = makeConstraintDropsIdempotent(content)\n content = content.replace(\n /export class (Migration\\d+)/,\n `export class $1_${modId.replace(/[^a-zA-Z0-9]/g, '_')}`\n )\n fs.writeFileSync(newPath, content, 'utf8')\n if (newPath !== orig) fs.unlinkSync(orig)\n usedFileNames.add(newPath)\n results.push(formatResult(modId, `generated ${path.basename(newPath)}`, ''))\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n results.push(formatResult(modId, `generated ${path.basename(diff.fileName)} (rename failed: ${message})`, ''))\n }\n } else {\n results.push(formatResult(modId, 'no changes', ''))\n }\n } finally {\n await orm.close(true)\n }\n }\n\n console.log(results.join('\\n'))\n await warnAboutMissingEncryptionMapBackfills(ordered, resolver)\n}\n\nexport async function dbMigrate(resolver: PackageResolver, options: DbOptions = {}): Promise<void> {\n const modules = resolver.loadEnabledModules()\n const ordered = sortModules(modules)\n const results: string[] = []\n const appliedMigrations: AppliedMigration[] = []\n\n try {\n for (const entry of ordered) {\n const modId = entry.id\n const sanitizedModId = sanitizeModuleId(modId)\n const entities = await loadModuleEntities(entry, resolver)\n\n const migrationsPath = getMigrationsPath(entry, resolver)\n\n // Skip if no entities AND no migrations directory exists\n // (allows @app modules to run migrations even if entities can't be dynamically imported)\n if (!entities.length && !fs.existsSync(migrationsPath)) continue\n fs.mkdirSync(migrationsPath, { recursive: true })\n\n const tableName = `mikro_orm_migrations_${sanitizedModId}`\n validateTableName(tableName)\n\n // dbMigrate only runs existing migration files \u2014 entities are intentionally\n // omitted so MikroORM does not compare them against the snapshot and\n // auto-generate a phantom diff migration (that would duplicate tables\n // already created by committed migrations).\n const sslConfig = getSslConfig()\n const orm = await MikroORM.init<PostgreSqlDriver>({\n driver: PostgreSqlDriver,\n clientUrl: getClientUrl(),\n loggerFactory: () => createMinimalLogger(),\n dynamicImportProvider,\n entities: [],\n metadataProvider: ReflectMetadataProvider,\n discovery: { warnWhenNoEntities: false },\n migrations: {\n path: migrationsPath,\n glob: '!(*.d).{ts,js}',\n tableName,\n snapshot: false,\n dropTables: false,\n },\n schemaGenerator: {\n disableForeignKeys: true,\n },\n pool: {\n min: 1,\n max: 3,\n idleTimeoutMillis: 30000,\n // acquireTimeoutMillis removed for v7 (pg.Pool doesn't support it; use connectionTimeoutMillis in driverOptions if needed)\n },\n driverOptions: sslConfig ? {\n ssl: sslConfig,\n } : undefined,\n })\n\n try {\n const migrator = orm.migrator as Migrator\n const pending = await migrator.getPending()\n if (!pending.length) {\n results.push(formatResult(modId, 'no pending migrations', ''))\n } else {\n const renderProgress = createProgressRenderer(pending.length)\n let applied = 0\n if (!QUIET_MODE) {\n process.stdout.write(` ${PROGRESS_EMOJI} ${modId}: ${renderProgress(applied)}`)\n }\n for (const migration of pending) {\n const migrationName =\n typeof migration === 'string'\n ? migration\n : (migration as any).name ?? (migration as any).fileName\n await migrator.up(migrationName ? { migrations: [migrationName] } : undefined)\n if (migrationName) {\n appliedMigrations.push({\n moduleId: modId,\n migrationsPath,\n name: String(migrationName),\n filePath: typeof migration === 'string' ? null : (migration as any).path ?? null,\n })\n }\n applied += 1\n if (!QUIET_MODE) {\n process.stdout.write(`\\r ${PROGRESS_EMOJI} ${modId}: ${renderProgress(applied)}`)\n }\n }\n if (!QUIET_MODE) process.stdout.write('\\n')\n results.push(\n formatResult(modId, `${pending.length} migration${pending.length === 1 ? '' : 's'} applied`, '')\n )\n }\n } finally {\n await orm.close(true)\n }\n }\n } finally {\n console.log(results.join('\\n'))\n await dischargeQueryIndexReindexRequests(appliedMigrations)\n }\n}\n\n/**\n * Runs after every module migrated and after `orm.close()`, so the rewrites the declarations\n * refer to are committed before the reindex is queued.\n *\n * Reached from a `finally`, so it also runs when a later migration threw: the migrations that\n * did apply already committed, and their declarations are the only record that their projections\n * are stale \u2014 a re-run cannot recover them, because `getPending()` no longer lists them. It must\n * therefore never throw, or it would replace the migration failure the operator needs to see.\n */\nexport async function dischargeQueryIndexReindexRequests(\n applied: readonly AppliedMigration[],\n): Promise<void> {\n if (!applied.length || !isMigrationReindexEnabled()) return\n\n try {\n const entityTypes = await collectQueryIndexReindexEntityTypes(applied, {\n importModule: dynamicImportProvider,\n onWarn: (message) => console.warn(message),\n })\n if (!entityTypes.length) return\n\n await requestQueryIndexReindex(entityTypes, {\n createContainer: async () => {\n const { createRequestContainer } = await import('@open-mercato/shared/lib/di/container')\n return (await createRequestContainer()) as any\n },\n onInfo: (message) => {\n if (!QUIET_MODE) console.log(message)\n },\n onWarn: (message) => console.warn(message),\n })\n } catch (error) {\n console.warn(\n `[query_index] Could not determine which projections the migrations invalidated: ${\n error instanceof Error ? error.message : String(error)\n }`,\n )\n }\n}\n\nexport async function dbGreenfield(resolver: PackageResolver, options: GreenfieldOptions): Promise<void> {\n if (!options.yes) {\n console.error('This command will DELETE all data. Use --yes to confirm.')\n process.exit(1)\n }\n\n console.log('Cleaning up migrations and snapshots for greenfield setup...')\n\n const modules = resolver.loadEnabledModules()\n const ordered = sortModules(modules)\n const results: string[] = []\n const outputDir = resolver.getOutputDir()\n\n for (const entry of ordered) {\n const modId = entry.id\n const migrationsPath = getMigrationsPath(entry, resolver)\n\n if (fs.existsSync(migrationsPath)) {\n // Remove all migration files\n const migrationFiles = fs\n .readdirSync(migrationsPath)\n .filter((file) => file.endsWith('.ts') && file.startsWith('Migration'))\n\n for (const file of migrationFiles) {\n fs.unlinkSync(path.join(migrationsPath, file))\n }\n\n // Remove snapshot files\n const snapshotFiles = fs\n .readdirSync(migrationsPath)\n .filter((file) => file.endsWith('.json') && file.includes('snapshot'))\n\n for (const file of snapshotFiles) {\n fs.unlinkSync(path.join(migrationsPath, file))\n }\n\n if (migrationFiles.length > 0 || snapshotFiles.length > 0) {\n results.push(\n formatResult(modId, `cleaned ${migrationFiles.length} migrations, ${snapshotFiles.length} snapshots`, '')\n )\n } else {\n results.push(formatResult(modId, 'already clean', ''))\n }\n } else {\n results.push(formatResult(modId, 'no migrations directory', ''))\n }\n\n // Clean up checksum files using glob pattern\n if (fs.existsSync(outputDir)) {\n const files = fs.readdirSync(outputDir)\n const checksumFiles = files.filter((file) => file.endsWith('.checksum'))\n\n for (const file of checksumFiles) {\n fs.unlinkSync(path.join(outputDir, file))\n }\n\n if (checksumFiles.length > 0) {\n results.push(formatResult(modId, `cleaned ${checksumFiles.length} checksum files`, ''))\n }\n }\n }\n\n console.log(results.join('\\n'))\n\n // Drop per-module MikroORM migration tables to ensure clean slate\n console.log('Dropping per-module migration tables...')\n try {\n const { Client } = await import('pg')\n const client = new Client({ connectionString: getClientUrl(), ssl: getSslConfig() })\n await client.connect()\n try {\n await client.query('BEGIN')\n for (const entry of ordered) {\n const modId = entry.id\n const sanitizedModId = sanitizeModuleId(modId)\n const tableName = `mikro_orm_migrations_${sanitizedModId}`\n validateTableName(tableName)\n await client.query(`DROP TABLE IF EXISTS \"${tableName}\"`)\n console.log(` ${modId}: dropped table ${tableName}`)\n }\n await client.query('COMMIT')\n } catch (e) {\n await client.query('ROLLBACK')\n throw e\n } finally {\n try {\n await client.end()\n } catch { }\n }\n } catch (e) {\n console.error('Failed to drop migration tables:', (e as any)?.message || e)\n throw e\n }\n\n // Drop all existing user tables to ensure fresh CREATE-only migrations\n console.log('Dropping ALL public tables for true greenfield...')\n try {\n const { Client } = await import('pg')\n const client = new Client({ connectionString: getClientUrl(), ssl: getSslConfig() })\n await client.connect()\n try {\n const res = await client.query(`SELECT tablename FROM pg_tables WHERE schemaname = current_schema()`)\n const tables: string[] = (res.rows || []).map((r: any) => String(r.tablename))\n if (tables.length) {\n await client.query('BEGIN')\n try {\n await client.query(\"SET session_replication_role = 'replica'\")\n for (const t of tables) {\n await client.query(`DROP TABLE IF EXISTS ${quotePostgresIdentifier(t)} CASCADE`)\n }\n await client.query(\"SET session_replication_role = 'origin'\")\n await client.query('COMMIT')\n console.log(` Dropped ${tables.length} tables.`)\n } catch (e) {\n await client.query('ROLLBACK')\n throw e\n }\n } else {\n console.log(' No tables found to drop.')\n }\n } finally {\n try {\n await client.end()\n } catch { }\n }\n } catch (e) {\n console.error('Failed to drop public tables:', (e as any)?.message || e)\n throw e\n }\n\n // Generate fresh migrations for all modules\n console.log('Generating fresh migrations for all modules...')\n await dbGenerate(resolver)\n\n // Apply migrations\n console.log('Applying migrations...')\n await dbMigrate(resolver)\n\n console.log('Greenfield reset complete! Fresh migrations generated and applied.')\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { parseBooleanWithDefault } from "@open-mercato/shared/lib/boolean";
|
|
2
|
+
function isEncryptionBackfillCheckEnabled(env = process.env) {
|
|
3
|
+
return parseBooleanWithDefault(env.OM_ENCRYPTION_BACKFILL_CHECK, true);
|
|
4
|
+
}
|
|
5
|
+
function readDeclaredEncryptionMaps(moduleId, moduleExports) {
|
|
6
|
+
if (!moduleExports || typeof moduleExports !== "object") return [];
|
|
7
|
+
const exportsRecord = moduleExports;
|
|
8
|
+
const maps = exportsRecord.defaultEncryptionMaps ?? exportsRecord.encryptionMaps ?? exportsRecord.default;
|
|
9
|
+
if (!Array.isArray(maps)) return [];
|
|
10
|
+
const declared = [];
|
|
11
|
+
for (const map of maps) {
|
|
12
|
+
if (!map || typeof map !== "object") continue;
|
|
13
|
+
const { entityId, keyScope, fields } = map;
|
|
14
|
+
if (typeof entityId !== "string" || keyScope === "system" || !Array.isArray(fields)) continue;
|
|
15
|
+
const fieldNames = fields.map((rule) => rule && typeof rule === "object" ? rule.field : null).filter((field) => typeof field === "string" && field.length > 0);
|
|
16
|
+
if (fieldNames.length) declared.push({ moduleId, entityId, fields: fieldNames });
|
|
17
|
+
}
|
|
18
|
+
return declared;
|
|
19
|
+
}
|
|
20
|
+
function readRuleFields(fieldsJson) {
|
|
21
|
+
const fields = /* @__PURE__ */ new Set();
|
|
22
|
+
if (!Array.isArray(fieldsJson)) return fields;
|
|
23
|
+
for (const rule of fieldsJson) {
|
|
24
|
+
if (rule && typeof rule === "object" && typeof rule.field === "string") {
|
|
25
|
+
fields.add(rule.field);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return fields;
|
|
29
|
+
}
|
|
30
|
+
function scopeKey(tenantId, organizationId) {
|
|
31
|
+
return `${tenantId ?? ""}|${organizationId ?? ""}`;
|
|
32
|
+
}
|
|
33
|
+
function mentionsLiteral(source, value) {
|
|
34
|
+
return source.includes(`'${value}'`) || source.includes(`"${value}"`) || source.includes(`\`${value}\``);
|
|
35
|
+
}
|
|
36
|
+
function isBackfilledByMigration(migrationSources, entityId, field) {
|
|
37
|
+
return migrationSources.some(
|
|
38
|
+
(source) => (source.includes("encryption_maps") || source.includes("buildEncryptionMapBackfillSql")) && mentionsLiteral(source, entityId) && mentionsLiteral(source, field)
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
function findEncryptionBackfillGaps(input) {
|
|
42
|
+
const activeScopes = /* @__PURE__ */ new Set();
|
|
43
|
+
const fieldsByScopeAndEntity = /* @__PURE__ */ new Map();
|
|
44
|
+
for (const row of input.rows) {
|
|
45
|
+
const scope = scopeKey(row.tenantId, row.organizationId);
|
|
46
|
+
if (row.isActive) activeScopes.add(scope);
|
|
47
|
+
const key = `${scope}|${row.entityId}`;
|
|
48
|
+
const fields = fieldsByScopeAndEntity.get(key) ?? /* @__PURE__ */ new Set();
|
|
49
|
+
for (const field of readRuleFields(row.fieldsJson)) fields.add(field);
|
|
50
|
+
fieldsByScopeAndEntity.set(key, fields);
|
|
51
|
+
}
|
|
52
|
+
if (activeScopes.size === 0) return [];
|
|
53
|
+
const gaps = [];
|
|
54
|
+
for (const map of input.declared) {
|
|
55
|
+
for (const field of map.fields) {
|
|
56
|
+
let missingScopes = 0;
|
|
57
|
+
for (const scope of activeScopes) {
|
|
58
|
+
if (!fieldsByScopeAndEntity.get(`${scope}|${map.entityId}`)?.has(field)) missingScopes += 1;
|
|
59
|
+
}
|
|
60
|
+
if (missingScopes === 0) continue;
|
|
61
|
+
if (isBackfilledByMigration(input.migrationSources, map.entityId, field)) continue;
|
|
62
|
+
gaps.push({ moduleId: map.moduleId, entityId: map.entityId, field, missingScopes, totalScopes: activeScopes.size });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return gaps;
|
|
66
|
+
}
|
|
67
|
+
const MAX_WARNING_ENTITIES = 20;
|
|
68
|
+
function formatEncryptionBackfillWarning(gaps) {
|
|
69
|
+
const byEntity = /* @__PURE__ */ new Map();
|
|
70
|
+
for (const gap of gaps) {
|
|
71
|
+
const entry = byEntity.get(gap.entityId) ?? { moduleId: gap.moduleId, fields: [], missingScopes: 0, totalScopes: gap.totalScopes };
|
|
72
|
+
entry.fields.push(gap.field);
|
|
73
|
+
entry.missingScopes = Math.max(entry.missingScopes, gap.missingScopes);
|
|
74
|
+
byEntity.set(gap.entityId, entry);
|
|
75
|
+
}
|
|
76
|
+
const entities = [...byEntity.entries()];
|
|
77
|
+
const lines = entities.slice(0, MAX_WARNING_ENTITIES).map(
|
|
78
|
+
([entityId, entry]) => ` - ${entry.moduleId}: ${entityId} [${entry.fields.join(", ")}] (missing in ${entry.missingScopes}/${entry.totalScopes} tenant scopes)`
|
|
79
|
+
);
|
|
80
|
+
if (entities.length > MAX_WARNING_ENTITIES) lines.push(` \u2026 and ${entities.length - MAX_WARNING_ENTITIES} more entities`);
|
|
81
|
+
return [
|
|
82
|
+
"\u26A0\uFE0F Encryption maps declared in encryption.ts are missing for existing tenants, and no migration backfills them:",
|
|
83
|
+
...lines,
|
|
84
|
+
" Tenants that already exist keep writing these fields as plaintext. If the module may already be deployed,",
|
|
85
|
+
" add a migration that calls buildEncryptionMapBackfillSql from @open-mercato/shared/lib/encryption/migration-backfill.",
|
|
86
|
+
" Local database only: `yarn mercato entities seed-encryption --tenant <id>`. Silence with OM_ENCRYPTION_BACKFILL_CHECK=off."
|
|
87
|
+
].join("\n");
|
|
88
|
+
}
|
|
89
|
+
async function checkEncryptionMapBackfills(deps) {
|
|
90
|
+
try {
|
|
91
|
+
const declared = await deps.loadDeclaredMaps();
|
|
92
|
+
if (!declared.length) return [];
|
|
93
|
+
const rows = await deps.queryEncryptionMaps();
|
|
94
|
+
if (!rows?.length) return [];
|
|
95
|
+
const gaps = findEncryptionBackfillGaps({ declared, rows, migrationSources: deps.loadMigrationSources() });
|
|
96
|
+
if (gaps.length) deps.warn(formatEncryptionBackfillWarning(gaps));
|
|
97
|
+
return gaps;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
100
|
+
deps.warn(`\u26A0\uFE0F Skipped the encryption map backfill check: ${message}`);
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export {
|
|
105
|
+
checkEncryptionMapBackfills,
|
|
106
|
+
findEncryptionBackfillGaps,
|
|
107
|
+
formatEncryptionBackfillWarning,
|
|
108
|
+
isBackfilledByMigration,
|
|
109
|
+
isEncryptionBackfillCheckEnabled,
|
|
110
|
+
readDeclaredEncryptionMaps
|
|
111
|
+
};
|
|
112
|
+
//# sourceMappingURL=encryption-backfill-check.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/lib/db/encryption-backfill-check.ts"],
|
|
4
|
+
"sourcesContent": ["import { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'\n\n/**\n * Advisory check run after `db:generate`. Tenant encryption maps are seeded only when a tenant is\n * created, so an `encryption.ts` entry added after tenants exist never reaches them unless a module\n * migration backfills it (`buildEncryptionMapBackfillSql`). The connected database is the evidence:\n * a declared `(entityId, field)` that some existing encryption-enabled scope lacks, and that no\n * migration mentions, is reported. The check only warns; it never fails generation.\n */\n\nexport type DeclaredEncryptionMap = {\n moduleId: string\n entityId: string\n fields: string[]\n}\n\nexport type EncryptionMapRow = {\n tenantId: string | null\n organizationId: string | null\n entityId: string\n fieldsJson: unknown\n isActive: boolean\n}\n\nexport type EncryptionBackfillGap = {\n moduleId: string\n entityId: string\n field: string\n missingScopes: number\n totalScopes: number\n}\n\nexport type EncryptionBackfillCheckDeps = {\n loadDeclaredMaps: () => Promise<DeclaredEncryptionMap[]>\n loadMigrationSources: () => string[]\n queryEncryptionMaps: () => Promise<EncryptionMapRow[] | null>\n warn: (message: string) => void\n}\n\nexport function isEncryptionBackfillCheckEnabled(env: NodeJS.ProcessEnv = process.env): boolean {\n return parseBooleanWithDefault(env.OM_ENCRYPTION_BACKFILL_CHECK, true)\n}\n\nexport function readDeclaredEncryptionMaps(moduleId: string, moduleExports: unknown): DeclaredEncryptionMap[] {\n if (!moduleExports || typeof moduleExports !== 'object') return []\n const exportsRecord = moduleExports as Record<string, unknown>\n const maps = exportsRecord.defaultEncryptionMaps ?? exportsRecord.encryptionMaps ?? exportsRecord.default\n if (!Array.isArray(maps)) return []\n const declared: DeclaredEncryptionMap[] = []\n for (const map of maps) {\n if (!map || typeof map !== 'object') continue\n const { entityId, keyScope, fields } = map as Record<string, unknown>\n if (typeof entityId !== 'string' || keyScope === 'system' || !Array.isArray(fields)) continue\n const fieldNames = fields\n .map((rule) => (rule && typeof rule === 'object' ? (rule as Record<string, unknown>).field : null))\n .filter((field): field is string => typeof field === 'string' && field.length > 0)\n if (fieldNames.length) declared.push({ moduleId, entityId, fields: fieldNames })\n }\n return declared\n}\n\nfunction readRuleFields(fieldsJson: unknown): Set<string> {\n const fields = new Set<string>()\n if (!Array.isArray(fieldsJson)) return fields\n for (const rule of fieldsJson) {\n if (rule && typeof rule === 'object' && typeof (rule as Record<string, unknown>).field === 'string') {\n fields.add((rule as Record<string, string>).field)\n }\n }\n return fields\n}\n\nfunction scopeKey(tenantId: string | null, organizationId: string | null): string {\n return `${tenantId ?? ''}|${organizationId ?? ''}`\n}\n\nfunction mentionsLiteral(source: string, value: string): boolean {\n return source.includes(`'${value}'`) || source.includes(`\"${value}\"`) || source.includes(`\\`${value}\\``)\n}\n\nexport function isBackfilledByMigration(migrationSources: readonly string[], entityId: string, field: string): boolean {\n return migrationSources.some(\n (source) =>\n (source.includes('encryption_maps') || source.includes('buildEncryptionMapBackfillSql')) &&\n mentionsLiteral(source, entityId) &&\n mentionsLiteral(source, field),\n )\n}\n\nexport function findEncryptionBackfillGaps(input: {\n declared: readonly DeclaredEncryptionMap[]\n rows: readonly EncryptionMapRow[]\n migrationSources: readonly string[]\n}): EncryptionBackfillGap[] {\n const activeScopes = new Set<string>()\n const fieldsByScopeAndEntity = new Map<string, Set<string>>()\n for (const row of input.rows) {\n const scope = scopeKey(row.tenantId, row.organizationId)\n if (row.isActive) activeScopes.add(scope)\n const key = `${scope}|${row.entityId}`\n const fields = fieldsByScopeAndEntity.get(key) ?? new Set<string>()\n for (const field of readRuleFields(row.fieldsJson)) fields.add(field)\n fieldsByScopeAndEntity.set(key, fields)\n }\n if (activeScopes.size === 0) return []\n\n const gaps: EncryptionBackfillGap[] = []\n for (const map of input.declared) {\n for (const field of map.fields) {\n let missingScopes = 0\n for (const scope of activeScopes) {\n if (!fieldsByScopeAndEntity.get(`${scope}|${map.entityId}`)?.has(field)) missingScopes += 1\n }\n if (missingScopes === 0) continue\n if (isBackfilledByMigration(input.migrationSources, map.entityId, field)) continue\n gaps.push({ moduleId: map.moduleId, entityId: map.entityId, field, missingScopes, totalScopes: activeScopes.size })\n }\n }\n return gaps\n}\n\nconst MAX_WARNING_ENTITIES = 20\n\nexport function formatEncryptionBackfillWarning(gaps: readonly EncryptionBackfillGap[]): string {\n const byEntity = new Map<string, { moduleId: string; fields: string[]; missingScopes: number; totalScopes: number }>()\n for (const gap of gaps) {\n const entry = byEntity.get(gap.entityId) ?? { moduleId: gap.moduleId, fields: [], missingScopes: 0, totalScopes: gap.totalScopes }\n entry.fields.push(gap.field)\n entry.missingScopes = Math.max(entry.missingScopes, gap.missingScopes)\n byEntity.set(gap.entityId, entry)\n }\n const entities = [...byEntity.entries()]\n const lines = entities\n .slice(0, MAX_WARNING_ENTITIES)\n .map(\n ([entityId, entry]) =>\n ` - ${entry.moduleId}: ${entityId} [${entry.fields.join(', ')}] (missing in ${entry.missingScopes}/${entry.totalScopes} tenant scopes)`,\n )\n if (entities.length > MAX_WARNING_ENTITIES) lines.push(` \u2026 and ${entities.length - MAX_WARNING_ENTITIES} more entities`)\n return [\n '\u26A0\uFE0F Encryption maps declared in encryption.ts are missing for existing tenants, and no migration backfills them:',\n ...lines,\n ' Tenants that already exist keep writing these fields as plaintext. If the module may already be deployed,',\n ' add a migration that calls buildEncryptionMapBackfillSql from @open-mercato/shared/lib/encryption/migration-backfill.',\n ' Local database only: `yarn mercato entities seed-encryption --tenant <id>`. Silence with OM_ENCRYPTION_BACKFILL_CHECK=off.',\n ].join('\\n')\n}\n\nexport async function checkEncryptionMapBackfills(deps: EncryptionBackfillCheckDeps): Promise<EncryptionBackfillGap[]> {\n try {\n const declared = await deps.loadDeclaredMaps()\n if (!declared.length) return []\n const rows = await deps.queryEncryptionMaps()\n if (!rows?.length) return []\n const gaps = findEncryptionBackfillGaps({ declared, rows, migrationSources: deps.loadMigrationSources() })\n if (gaps.length) deps.warn(formatEncryptionBackfillWarning(gaps))\n return gaps\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n deps.warn(`\u26A0\uFE0F Skipped the encryption map backfill check: ${message}`)\n return []\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
3
4
|
import { parseBooleanWithDefault } from "@open-mercato/shared/lib/boolean";
|
|
4
5
|
import {
|
|
5
6
|
formatQueryIndexRebuildCommands,
|
|
@@ -33,7 +34,7 @@ async function collectQueryIndexReindexEntityTypes(applied, deps) {
|
|
|
33
34
|
if (!filePath) continue;
|
|
34
35
|
let moduleExports;
|
|
35
36
|
try {
|
|
36
|
-
moduleExports = await deps.importModule(filePath);
|
|
37
|
+
moduleExports = await deps.importModule(pathToFileURL(path.resolve(filePath)).href);
|
|
37
38
|
} catch (error) {
|
|
38
39
|
deps.onWarn?.(
|
|
39
40
|
`[query_index] Could not read reindex declarations from ${migration.moduleId}/${migration.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/db/migration-reindex.ts"],
|
|
4
|
-
"sourcesContent": ["import fs from 'node:fs'\nimport path from 'node:path'\nimport { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'\nimport {\n formatQueryIndexRebuildCommands,\n readQueryIndexReindexDeclaration,\n} from '@open-mercato/shared/lib/query/migration-reindex'\n\nconst MIGRATION_FILE_NAME_PATTERN = /^[A-Za-z0-9._-]+$/\nconst MIGRATION_EXTENSIONS = ['.ts', '.js', '.mjs', '.cjs']\n\nexport type AppliedMigration = {\n moduleId: string\n migrationsPath: string\n name: string\n filePath?: string | null\n}\n\nexport type MigrationReindexDeps = {\n importModule: (filePath: string) => Promise<unknown>\n fileExists?: (filePath: string) => boolean\n onWarn?: (message: string) => void\n}\n\nexport function isMigrationReindexEnabled(env: NodeJS.ProcessEnv = process.env): boolean {\n return parseBooleanWithDefault(env.OM_MIGRATION_REINDEX, true)\n}\n\nexport function resolveMigrationFilePath(\n migrationsPath: string,\n migrationName: string,\n knownPath?: string | null,\n fileExists: (filePath: string) => boolean = fs.existsSync,\n): string | null {\n if (knownPath && fileExists(knownPath)) return knownPath\n const stem = path.basename(migrationName).replace(/\\.(ts|js|mjs|cjs)$/i, '')\n if (!stem || !MIGRATION_FILE_NAME_PATTERN.test(stem)) return null\n for (const extension of MIGRATION_EXTENSIONS) {\n const candidate = path.join(migrationsPath, `${stem}${extension}`)\n if (fileExists(candidate)) return candidate\n }\n return null\n}\n\nexport async function collectQueryIndexReindexEntityTypes(\n applied: readonly AppliedMigration[],\n deps: MigrationReindexDeps,\n): Promise<string[]> {\n const fileExists = deps.fileExists ?? fs.existsSync\n const collected: string[] = []\n for (const migration of applied) {\n const filePath = resolveMigrationFilePath(\n migration.migrationsPath,\n migration.name,\n migration.filePath,\n fileExists,\n )\n if (!filePath) continue\n let moduleExports: unknown\n try {\n moduleExports = await deps.importModule(filePath)\n } catch (error) {\n deps.onWarn?.(\n `[query_index] Could not read reindex declarations from ${migration.moduleId}/${migration.name}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n )\n continue\n }\n const declared = readQueryIndexReindexDeclaration(moduleExports, (rejected: unknown) => {\n deps.onWarn?.(\n `[query_index] Ignoring an invalid reindex declaration in ${migration.moduleId}/${migration.name}: ${\n JSON.stringify(rejected) ?? String(rejected)\n } is not a \"module:entity\" identifier, so its projection will NOT be rebuilt.`,\n )\n })\n for (const entityType of declared) {\n if (!collected.includes(entityType)) collected.push(entityType)\n }\n }\n return collected\n}\n\nexport function formatManualReindexInstructions(entityTypes: readonly string[]): string {\n const commands = formatQueryIndexRebuildCommands(entityTypes)\n .map((command) => ` ${command}`)\n .join('\\n')\n return [\n '[query_index] Could not queue the reindex requested by the migrations that just ran.',\n ' Their query-index projections still hold pre-migration values until you run:',\n commands,\n ].join('\\n')\n}\n\nexport type QueryIndexReindexEmitter = {\n emitEvent: (event: string, payload: Record<string, unknown>, options?: { persistent?: boolean }) => Promise<void>\n}\n\nexport type QueryIndexReindexContainer = {\n resolve: (name: string) => unknown\n dispose?: () => Promise<void> | void\n}\n\nexport type RequestQueryIndexReindexDeps = {\n createContainer: () => Promise<QueryIndexReindexContainer>\n onInfo?: (message: string) => void\n onWarn?: (message: string) => void\n}\n\n/**\n * Queued rather than executed inline: a rebuild of a large entity type must not hold up a\n * deploy's migration step. `force` stays false so the existing `query_index.reindex`\n * subscriber upserts in place \u2014 a forced run purges the entity's rows first, which would\n * leave the projection empty for the duration of the rebuild.\n */\nexport async function requestQueryIndexReindex(\n entityTypes: readonly string[],\n deps: RequestQueryIndexReindexDeps,\n): Promise<{ requested: string[]; queued: boolean }> {\n if (!entityTypes.length) return { requested: [], queued: false }\n\n let container: QueryIndexReindexContainer | null = null\n const queued: string[] = []\n try {\n container = await deps.createContainer()\n const eventBus = container.resolve('eventBus') as QueryIndexReindexEmitter\n for (const entityType of entityTypes) {\n await eventBus.emitEvent(\n 'query_index.reindex',\n { entityType, allowAllTenants: true, force: false },\n { persistent: true },\n )\n queued.push(entityType)\n }\n deps.onInfo?.(\n `[query_index] Queued a reindex for ${queued.length} entity type(s) touched by data migrations: ${queued.join(', ')}`,\n )\n return { requested: queued, queued: true }\n } catch (error) {\n const unqueued = entityTypes.filter((entityType) => !queued.includes(entityType))\n deps.onWarn?.(\n `${formatManualReindexInstructions(unqueued)}\\n Reason: ${\n error instanceof Error ? error.message : String(error)\n }`,\n )\n return { requested: queued, queued: false }\n } finally {\n if (container && typeof container.dispose === 'function') {\n try {\n await container.dispose()\n } catch {\n // A container that fails to dispose must not turn a successful migration into a failure.\n }\n }\n }\n}\n"],
|
|
5
|
-
"mappings": "AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,+BAA+B;AACxC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AAEP,MAAM,8BAA8B;AACpC,MAAM,uBAAuB,CAAC,OAAO,OAAO,QAAQ,MAAM;AAenD,SAAS,0BAA0B,MAAyB,QAAQ,KAAc;AACvF,SAAO,wBAAwB,IAAI,sBAAsB,IAAI;AAC/D;AAEO,SAAS,yBACd,gBACA,eACA,WACA,aAA4C,GAAG,YAChC;AACf,MAAI,aAAa,WAAW,SAAS,EAAG,QAAO;AAC/C,QAAM,OAAO,KAAK,SAAS,aAAa,EAAE,QAAQ,uBAAuB,EAAE;AAC3E,MAAI,CAAC,QAAQ,CAAC,4BAA4B,KAAK,IAAI,EAAG,QAAO;AAC7D,aAAW,aAAa,sBAAsB;AAC5C,UAAM,YAAY,KAAK,KAAK,gBAAgB,GAAG,IAAI,GAAG,SAAS,EAAE;AACjE,QAAI,WAAW,SAAS,EAAG,QAAO;AAAA,EACpC;AACA,SAAO;AACT;AAEA,eAAsB,oCACpB,SACA,MACmB;AACnB,QAAM,aAAa,KAAK,cAAc,GAAG;AACzC,QAAM,YAAsB,CAAC;AAC7B,aAAW,aAAa,SAAS;AAC/B,UAAM,WAAW;AAAA,MACf,UAAU;AAAA,MACV,UAAU;AAAA,MACV,UAAU;AAAA,MACV;AAAA,IACF;AACA,QAAI,CAAC,SAAU;AACf,QAAI;AACJ,QAAI;AACF,sBAAgB,MAAM,KAAK,aAAa,QAAQ;AAAA,
|
|
4
|
+
"sourcesContent": ["import fs from 'node:fs'\nimport path from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'\nimport {\n formatQueryIndexRebuildCommands,\n readQueryIndexReindexDeclaration,\n} from '@open-mercato/shared/lib/query/migration-reindex'\n\nconst MIGRATION_FILE_NAME_PATTERN = /^[A-Za-z0-9._-]+$/\nconst MIGRATION_EXTENSIONS = ['.ts', '.js', '.mjs', '.cjs']\n\nexport type AppliedMigration = {\n moduleId: string\n migrationsPath: string\n name: string\n filePath?: string | null\n}\n\nexport type MigrationReindexDeps = {\n importModule: (filePath: string) => Promise<unknown>\n fileExists?: (filePath: string) => boolean\n onWarn?: (message: string) => void\n}\n\nexport function isMigrationReindexEnabled(env: NodeJS.ProcessEnv = process.env): boolean {\n return parseBooleanWithDefault(env.OM_MIGRATION_REINDEX, true)\n}\n\nexport function resolveMigrationFilePath(\n migrationsPath: string,\n migrationName: string,\n knownPath?: string | null,\n fileExists: (filePath: string) => boolean = fs.existsSync,\n): string | null {\n if (knownPath && fileExists(knownPath)) return knownPath\n const stem = path.basename(migrationName).replace(/\\.(ts|js|mjs|cjs)$/i, '')\n if (!stem || !MIGRATION_FILE_NAME_PATTERN.test(stem)) return null\n for (const extension of MIGRATION_EXTENSIONS) {\n const candidate = path.join(migrationsPath, `${stem}${extension}`)\n if (fileExists(candidate)) return candidate\n }\n return null\n}\n\nexport async function collectQueryIndexReindexEntityTypes(\n applied: readonly AppliedMigration[],\n deps: MigrationReindexDeps,\n): Promise<string[]> {\n const fileExists = deps.fileExists ?? fs.existsSync\n const collected: string[] = []\n for (const migration of applied) {\n const filePath = resolveMigrationFilePath(\n migration.migrationsPath,\n migration.name,\n migration.filePath,\n fileExists,\n )\n if (!filePath) continue\n let moduleExports: unknown\n try {\n moduleExports = await deps.importModule(pathToFileURL(path.resolve(filePath)).href)\n } catch (error) {\n deps.onWarn?.(\n `[query_index] Could not read reindex declarations from ${migration.moduleId}/${migration.name}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n )\n continue\n }\n const declared = readQueryIndexReindexDeclaration(moduleExports, (rejected: unknown) => {\n deps.onWarn?.(\n `[query_index] Ignoring an invalid reindex declaration in ${migration.moduleId}/${migration.name}: ${\n JSON.stringify(rejected) ?? String(rejected)\n } is not a \"module:entity\" identifier, so its projection will NOT be rebuilt.`,\n )\n })\n for (const entityType of declared) {\n if (!collected.includes(entityType)) collected.push(entityType)\n }\n }\n return collected\n}\n\nexport function formatManualReindexInstructions(entityTypes: readonly string[]): string {\n const commands = formatQueryIndexRebuildCommands(entityTypes)\n .map((command) => ` ${command}`)\n .join('\\n')\n return [\n '[query_index] Could not queue the reindex requested by the migrations that just ran.',\n ' Their query-index projections still hold pre-migration values until you run:',\n commands,\n ].join('\\n')\n}\n\nexport type QueryIndexReindexEmitter = {\n emitEvent: (event: string, payload: Record<string, unknown>, options?: { persistent?: boolean }) => Promise<void>\n}\n\nexport type QueryIndexReindexContainer = {\n resolve: (name: string) => unknown\n dispose?: () => Promise<void> | void\n}\n\nexport type RequestQueryIndexReindexDeps = {\n createContainer: () => Promise<QueryIndexReindexContainer>\n onInfo?: (message: string) => void\n onWarn?: (message: string) => void\n}\n\n/**\n * Queued rather than executed inline: a rebuild of a large entity type must not hold up a\n * deploy's migration step. `force` stays false so the existing `query_index.reindex`\n * subscriber upserts in place \u2014 a forced run purges the entity's rows first, which would\n * leave the projection empty for the duration of the rebuild.\n */\nexport async function requestQueryIndexReindex(\n entityTypes: readonly string[],\n deps: RequestQueryIndexReindexDeps,\n): Promise<{ requested: string[]; queued: boolean }> {\n if (!entityTypes.length) return { requested: [], queued: false }\n\n let container: QueryIndexReindexContainer | null = null\n const queued: string[] = []\n try {\n container = await deps.createContainer()\n const eventBus = container.resolve('eventBus') as QueryIndexReindexEmitter\n for (const entityType of entityTypes) {\n await eventBus.emitEvent(\n 'query_index.reindex',\n { entityType, allowAllTenants: true, force: false },\n { persistent: true },\n )\n queued.push(entityType)\n }\n deps.onInfo?.(\n `[query_index] Queued a reindex for ${queued.length} entity type(s) touched by data migrations: ${queued.join(', ')}`,\n )\n return { requested: queued, queued: true }\n } catch (error) {\n const unqueued = entityTypes.filter((entityType) => !queued.includes(entityType))\n deps.onWarn?.(\n `${formatManualReindexInstructions(unqueued)}\\n Reason: ${\n error instanceof Error ? error.message : String(error)\n }`,\n )\n return { requested: queued, queued: false }\n } finally {\n if (container && typeof container.dispose === 'function') {\n try {\n await container.dispose()\n } catch {\n // A container that fails to dispose must not turn a successful migration into a failure.\n }\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/events-single-delivery.ts"],
|
|
4
|
-
"sourcesContent": ["import { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'\nimport type { AutoSpawnWorkersMode } from './auto-spawn-workers'\n\n// Keep these env names in sync with the runtime source of truth,\n// `@open-mercato/events/single-delivery`.
|
|
5
|
-
"mappings": "AAAA,SAAS,+BAA+B;
|
|
4
|
+
"sourcesContent": ["import { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'\nimport type { AutoSpawnWorkersMode } from './auto-spawn-workers'\n\n// Keep these env names in sync with the runtime source of truth,\n// `@open-mercato/events/single-delivery`. This guard runs ahead of app\n// bootstrap (before the Postgres driver and the rest of the module graph\n// load), so it deliberately avoids importing `@open-mercato/events` here even\n// though the package is now a declared dependency (bin.ts's post-bootstrap\n// exit-flush uses it) \u2014 the reconcile logic is mirrored instead for this\n// server-bootstrap guard only; the bus and worker own the runtime behavior.\n//\n// This guard is the ONLY place that reconciles the flag against worker\n// availability. The event bus deliberately does not: the queue is durable, so a\n// persistent emit with no worker yet is delayed rather than lost, and delivering\n// inline instead would move the work onto the caller's request path. Only this\n// bootstrap knows for a fact that the process it is launching runs no worker, so\n// only it rewrites the value - into both envs below, so the app and any spawned\n// child agree.\nconst EVENTS_SINGLE_DELIVERY_ENV = 'OM_EVENTS_SINGLE_DELIVERY'\nconst EVENTS_EXTERNAL_WORKER_ENV = 'OM_EVENTS_EXTERNAL_WORKER'\n\ntype EnvSource = NodeJS.ProcessEnv | Record<string, string | undefined>\n\nexport type SingleDeliveryReconciliation = {\n effective: boolean\n warning?: string\n}\n\n/**\n * Server-bootstrap guard for the default-on single-delivery dispatch.\n *\n * With single-delivery on, persistent subscribers are skipped inline and ONLY\n * the events worker dispatches them. A process that runs NO events worker\n * (auto-spawn off and no acknowledged external worker) would skip those\n * subscribers with nothing to drain the queue \u2014 silently dropping notifications,\n * queued emails, and indexing. This fails safe by disabling single-delivery for\n * such a process (persistent subscribers run inline instead) and returning a loud\n * warning. The bus stamps the queued job, so the inline run stays exactly-once.\n *\n * Transient worker downtime is NOT this guard's concern: the durable queue holds\n * the job until a worker returns. Only the \"no worker at all\" config is guarded.\n */\nexport function reconcileEventsSingleDelivery(\n env: EnvSource,\n autoSpawnWorkersMode: AutoSpawnWorkersMode,\n): SingleDeliveryReconciliation {\n const requested = parseBooleanWithDefault(env[EVENTS_SINGLE_DELIVERY_ENV], true)\n if (!requested) return { effective: false }\n const externalWorker = parseBooleanWithDefault(env[EVENTS_EXTERNAL_WORKER_ENV], false)\n const workersAvailable = autoSpawnWorkersMode !== 'off' || externalWorker\n if (workersAvailable) return { effective: true }\n return {\n effective: false,\n warning:\n `[events] ${EVENTS_SINGLE_DELIVERY_ENV} is on (default) but this process auto-spawns no events worker ` +\n `(AUTO_SPAWN_WORKERS=off) and ${EVENTS_EXTERNAL_WORKER_ENV} is not set. Persistent subscribers would be ` +\n `skipped inline with nothing to drain the queue, silently dropping notifications, queued emails, and ` +\n `indexing. Running them inline instead, on the caller's request path. Delivery stays exactly-once: the ` +\n `queued job is stamped so an events worker that does drain the queue skips it. To move this work off the ` +\n `request path, run an events worker (\\`mercato queue worker events\\`) and set ` +\n `${EVENTS_EXTERNAL_WORKER_ENV}=true, or enable AUTO_SPAWN_WORKERS.`,\n }\n}\n\n/**\n * Applies {@link reconcileEventsSingleDelivery} and writes the effective value\n * into both the current process env (for the in-process app/bus) and the spawned\n * worker/child env (so a child worker reads the same value the bus does). Logs\n * the warning once when single-delivery is disabled for safety.\n */\nexport function applyEventsSingleDeliveryGuard(args: {\n processEnv: NodeJS.ProcessEnv\n runtimeEnv: NodeJS.ProcessEnv\n autoSpawnWorkersMode: AutoSpawnWorkersMode\n}): SingleDeliveryReconciliation {\n const result = reconcileEventsSingleDelivery(args.processEnv, args.autoSpawnWorkersMode)\n if (result.warning) console.error(result.warning)\n const value = result.effective ? 'true' : 'false'\n args.processEnv[EVENTS_SINGLE_DELIVERY_ENV] = value\n args.runtimeEnv[EVENTS_SINGLE_DELIVERY_ENV] = value\n return result\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,+BAA+B;AAkBxC,MAAM,6BAA6B;AACnC,MAAM,6BAA6B;AAuB5B,SAAS,8BACd,KACA,sBAC8B;AAC9B,QAAM,YAAY,wBAAwB,IAAI,0BAA0B,GAAG,IAAI;AAC/E,MAAI,CAAC,UAAW,QAAO,EAAE,WAAW,MAAM;AAC1C,QAAM,iBAAiB,wBAAwB,IAAI,0BAA0B,GAAG,KAAK;AACrF,QAAM,mBAAmB,yBAAyB,SAAS;AAC3D,MAAI,iBAAkB,QAAO,EAAE,WAAW,KAAK;AAC/C,SAAO;AAAA,IACL,WAAW;AAAA,IACX,SACE,YAAY,0BAA0B,+FACN,0BAA0B,+aAKvD,0BAA0B;AAAA,EACjC;AACF;AAQO,SAAS,+BAA+B,MAId;AAC/B,QAAM,SAAS,8BAA8B,KAAK,YAAY,KAAK,oBAAoB;AACvF,MAAI,OAAO,QAAS,SAAQ,MAAM,OAAO,OAAO;AAChD,QAAM,QAAQ,OAAO,YAAY,SAAS;AAC1C,OAAK,WAAW,0BAA0B,IAAI;AAC9C,OAAK,WAAW,0BAA0B,IAAI;AAC9C,SAAO;AACT;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { flushTelemetry } from "./flush-telemetry.js";
|
|
2
|
+
async function flushPendingBrowserBroadcasts() {
|
|
3
|
+
const { BROADCAST_FLUSH_DEADLINE_MS, flushPendingBroadcasts } = await import("@open-mercato/events/broadcast-coalescer");
|
|
4
|
+
await flushPendingBroadcasts({ deadlineMs: BROADCAST_FLUSH_DEADLINE_MS });
|
|
5
|
+
}
|
|
6
|
+
async function flushBeforeExit(steps = {}) {
|
|
7
|
+
const flushBroadcasts = steps.flushBroadcasts ?? flushPendingBrowserBroadcasts;
|
|
8
|
+
try {
|
|
9
|
+
await flushBroadcasts();
|
|
10
|
+
} catch (error) {
|
|
11
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
12
|
+
console.warn(`[events] Broadcast flush on exit failed; the last browser refresh may be lost: ${reason}`);
|
|
13
|
+
}
|
|
14
|
+
await (steps.flushTelemetry ?? flushTelemetry)();
|
|
15
|
+
}
|
|
16
|
+
export {
|
|
17
|
+
flushBeforeExit
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=flush-before-exit.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/flush-before-exit.ts"],
|
|
4
|
+
"sourcesContent": ["import { flushTelemetry } from './flush-telemetry'\n\nexport type ExitFlushSteps = {\n flushBroadcasts: () => Promise<void>\n flushTelemetry: () => Promise<void>\n}\n\nasync function flushPendingBrowserBroadcasts(): Promise<void> {\n const { BROADCAST_FLUSH_DEADLINE_MS, flushPendingBroadcasts } = await import(\n '@open-mercato/events/broadcast-coalescer'\n )\n await flushPendingBroadcasts({ deadlineMs: BROADCAST_FLUSH_DEADLINE_MS })\n}\n\n/**\n * Runs before a returning CLI command's explicit `process.exit()`, which Node\n * skips `beforeExit` for \u2014 the natural-exit path the broadcast-coalescer\n * shutdown hook relies on to deliver a burst's trailing browser dispatch.\n *\n * The broadcast flush runs FIRST so its final `pg_notify` and the telemetry it\n * produces are still captured by the telemetry flush that follows. It is capped\n * at BROADCAST_FLUSH_DEADLINE_MS (3000ms), so a stalled Postgres connection at\n * exit degrades to a dropped tail instead of hanging the command. Both steps are\n * best-effort: neither can reject, and neither changes the command's exit code.\n *\n * Imported from the `broadcast-coalescer` deep path, and only after `run()` has\n * bootstrapped the app, so it never loads the Postgres driver ahead of\n * `initTelemetry()`.\n */\nexport async function flushBeforeExit(steps: Partial<ExitFlushSteps> = {}): Promise<void> {\n const flushBroadcasts = steps.flushBroadcasts ?? flushPendingBrowserBroadcasts\n try {\n await flushBroadcasts()\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error)\n console.warn(`[events] Broadcast flush on exit failed; the last browser refresh may be lost: ${reason}`)\n }\n await (steps.flushTelemetry ?? flushTelemetry)()\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,sBAAsB;AAO/B,eAAe,gCAA+C;AAC5D,QAAM,EAAE,6BAA6B,uBAAuB,IAAI,MAAM,OACpE,0CACF;AACA,QAAM,uBAAuB,EAAE,YAAY,4BAA4B,CAAC;AAC1E;AAiBA,eAAsB,gBAAgB,QAAiC,CAAC,GAAkB;AACxF,QAAM,kBAAkB,MAAM,mBAAmB;AACjD,MAAI;AACF,UAAM,gBAAgB;AAAA,EACxB,SAAS,OAAO;AACd,UAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACpE,YAAQ,KAAK,kFAAkF,MAAM,EAAE;AAAA,EACzG;AACA,SAAO,MAAM,kBAAkB,gBAAgB;AACjD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|