@open-mercato/shared 0.6.6-develop.6383.1.27fcc83455 → 0.6.6-develop.6385.1.9a81faa5f0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +1 -0
- package/dist/lib/modules/resource-usage.js +904 -0
- package/dist/lib/modules/resource-usage.js.map +7 -0
- package/dist/lib/number.js +14 -0
- package/dist/lib/number.js.map +7 -0
- package/dist/lib/search/config.js +2 -5
- package/dist/lib/search/config.js.map +2 -2
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/dist/modules/registry.js.map +2 -2
- package/package.json +2 -2
- package/src/lib/__tests__/number.test.ts +33 -0
- package/src/lib/modules/__tests__/resource-usage.test.ts +351 -0
- package/src/lib/modules/resource-usage.ts +1213 -0
- package/src/lib/number.ts +14 -0
- package/src/lib/search/config.ts +2 -5
- package/src/modules/registry.ts +2 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/lib/modules/resource-usage.ts"],
|
|
4
|
+
"sourcesContent": ["import fs from 'node:fs'\nimport path from 'node:path'\nimport { parseBooleanWithDefault } from '../boolean'\nimport { parseNumberWithDefault } from '../number'\n\nconst GLOBAL_KEY = '__openMercatoModuleResourceUsage__'\nconst NS_PER_MS = 1_000_000\nconst MICROS_PER_MS = 1000\nconst DEFAULT_RECENT_SAMPLE_LIMIT = 128\nconst DEFAULT_TOP_OPERATIONS_LIMIT = 5\nconst TIME_BUCKET_RETENTION_MS = 24 * 60 * 60 * 1000\nconst SNAPSHOT_THROTTLE_MS = 5_000\nconst SNAPSHOT_MAX_AGE_MS = 24 * 60 * 60 * 1000\nconst MINUTE_MS = 60 * 1000\nconst MODULE_RESOURCE_USAGE_BUCKET_INTERVAL_MS = 5 * MINUTE_MS\nconst MODULE_RESOURCE_USAGE_STARTUP_STAGE_MS = 5 * MINUTE_MS\n\nexport type ModuleResourceSurface = 'api' | 'subscriber' | 'worker' | 'custom'\n\nexport type ModuleResourceUsageInput = {\n moduleId?: string | null\n surface: ModuleResourceSurface\n operation: string\n resourceId?: string | null\n}\n\nexport type ModuleResourceUsageEntry = {\n moduleId: string\n surface: ModuleResourceSurface\n operation: string\n resourceId: string | null\n calls: number\n errors: number\n // How many of `calls` overlapped in wall-clock time with another tracked call anywhere in\n // the process. process.cpuUsage()/process.memoryUsage() are process-wide, so a call's own\n // CPU/heap/RSS deltas can include work actually done by a concurrent call during that window.\n // A high concurrentCalls share means the CPU/heap/RSS numbers here are less trustworthy as\n // this specific operation's own cost.\n concurrentCalls: number\n totalDurationMs: number\n maxDurationMs: number\n p95DurationMs: number\n totalCpuUserMs: number\n totalCpuSystemMs: number\n maxCpuMs: number\n totalHeapDeltaBytes: number\n positiveHeapDeltaBytes: number\n maxHeapDeltaBytes: number\n totalRssDeltaBytes: number\n positiveRssDeltaBytes: number\n maxRssDeltaBytes: number\n firstSeenAt: string\n lastSeenAt: string\n}\n\nexport type ModuleResourceUsageSurfaceSummary = {\n surface: ModuleResourceSurface\n calls: number\n errors: number\n totalDurationMs: number\n p95DurationMs: number\n totalCpuMs: number\n positiveHeapDeltaBytes: number\n positiveRssDeltaBytes: number\n}\n\nexport type ModuleResourceUsageModuleSummary = {\n moduleId: string\n calls: number\n errors: number\n totalDurationMs: number\n p95DurationMs: number\n totalCpuMs: number\n positiveHeapDeltaBytes: number\n positiveRssDeltaBytes: number\n surfaces: ModuleResourceUsageSurfaceSummary[]\n topOperations: ModuleResourceUsageEntry[]\n candidateReasons: string[]\n}\n\nexport type ModuleResourceUsageTimeBucketModule = {\n moduleId: string\n calls: number\n errors: number\n totalDurationMs: number\n p95DurationMs: number\n totalCpuMs: number\n positiveHeapDeltaBytes: number\n positiveRssDeltaBytes: number\n surfaces: ModuleResourceUsageSurfaceSummary[]\n topOperations: ModuleResourceUsageEntry[]\n candidateReasons: string[]\n}\n\nexport type ModuleResourceUsageTimeBucket = {\n bucketStart: string\n bucketEnd: string\n bucketIntervalMs: number\n stage: 'startup' | 'running'\n partial: boolean\n totals: {\n modules: number\n calls: number\n errors: number\n totalDurationMs: number\n totalCpuMs: number\n positiveHeapDeltaBytes: number\n positiveRssDeltaBytes: number\n }\n modules: ModuleResourceUsageTimeBucketModule[]\n}\n\nexport type ModuleResourceUsageReport = {\n generatedAt: string\n startedAt: string\n enabled: boolean\n bucketIntervalMs: number\n totals: {\n modules: number\n operations: number\n calls: number\n errors: number\n totalDurationMs: number\n totalCpuMs: number\n positiveHeapDeltaBytes: number\n positiveRssDeltaBytes: number\n }\n thresholds: ModuleResourceUsageThresholds\n modules: ModuleResourceUsageModuleSummary[]\n candidates: ModuleResourceUsageModuleSummary[]\n buckets: ModuleResourceUsageTimeBucket[]\n}\n\nexport type ModuleResourceUsageThresholds = {\n p95DurationMs: number\n cpuMs: number\n positiveHeapDeltaBytes: number\n positiveRssDeltaBytes: number\n errors: number\n}\n\ntype MutableEntry = Omit<ModuleResourceUsageEntry, 'p95DurationMs'> & {\n recentDurationsMs: number[]\n}\n\ntype MutableTimeBucketModule = Omit<ModuleResourceUsageTimeBucketModule, 'p95DurationMs' | 'surfaces' | 'topOperations' | 'candidateReasons'> & {\n recentDurationsMs: number[]\n operations: Map<string, MutableEntry>\n}\n\ntype MutableTimeBucket = {\n bucketStartMs: number\n bucketEndMs: number\n modules: Map<string, MutableTimeBucketModule>\n}\n\n// A live token per currently in-flight withModuleResourceUsage call. Used to detect when two\n// tracked calls overlap in wall-clock time, since process.cpuUsage()/process.memoryUsage() can't\n// distinguish which concurrent call actually did the work (see ModuleResourceUsageEntry.concurrentCalls).\ntype ActiveCallToken = { tainted: boolean }\n\ntype ResourceUsageState = {\n startedAt: string\n entries: Map<string, MutableEntry>\n buckets: Map<string, MutableTimeBucket>\n activeCalls: Set<ActiveCallToken>\n lastSnapshotAt: number\n shutdownHookRegistered: boolean\n // The bucket interval buckets were last re-keyed for. migrateMutableTimeBuckets only needs to\n // run again if this stops matching moduleResourceUsageBucketIntervalMs() (e.g. a dev HMR reload\n // picked up a source change to the interval constant) \u2014 it's a hardcoded constant today, so in\n // the common case this lets getState() skip rebuilding the whole buckets map on every call.\n migratedIntervalMs: number\n}\n\nfunction getState(): ResourceUsageState {\n const existing = (globalThis as Record<string, unknown>)[GLOBAL_KEY]\n if (existing && typeof existing === 'object' && (existing as ResourceUsageState).entries instanceof Map) {\n const state = existing as ResourceUsageState\n if (!(state.buckets instanceof Map)) state.buckets = new Map()\n if (!(state.activeCalls instanceof Set)) state.activeCalls = new Set()\n const currentIntervalMs = moduleResourceUsageBucketIntervalMs()\n if (state.migratedIntervalMs !== currentIntervalMs) {\n migrateMutableTimeBuckets(state, currentIntervalMs)\n state.migratedIntervalMs = currentIntervalMs\n }\n registerSnapshotShutdownHook(state)\n return state\n }\n const state: ResourceUsageState = {\n startedAt: new Date().toISOString(),\n entries: new Map(),\n buckets: new Map(),\n activeCalls: new Set(),\n lastSnapshotAt: 0,\n shutdownHookRegistered: false,\n migratedIntervalMs: moduleResourceUsageBucketIntervalMs(),\n }\n ;(globalThis as Record<string, unknown>)[GLOBAL_KEY] = state\n registerSnapshotShutdownHook(state)\n return state\n}\n\nfunction migrateMutableTimeBuckets(state: ResourceUsageState, intervalMs: number): void {\n const migratedBuckets = new Map<string, MutableTimeBucket>()\n for (const bucket of state.buckets.values()) {\n if (!(bucket.modules instanceof Map)) continue\n const bucketStartMs = Number.isFinite(bucket.bucketStartMs)\n ? Math.floor(bucket.bucketStartMs / intervalMs) * intervalMs\n : null\n if (bucketStartMs === null) continue\n const key = String(bucketStartMs)\n const targetBucket = migratedBuckets.get(key) ?? {\n bucketStartMs,\n bucketEndMs: bucketStartMs + intervalMs,\n modules: new Map(),\n }\n mergeMutableTimeBucket(targetBucket, bucket)\n migratedBuckets.set(key, targetBucket)\n }\n state.buckets = migratedBuckets\n}\n\nfunction trimRecentDurations(values: number[]): number[] {\n if (values.length <= DEFAULT_RECENT_SAMPLE_LIMIT) return values\n return values.slice(values.length - DEFAULT_RECENT_SAMPLE_LIMIT)\n}\n\nfunction mergeMutableEntry(target: MutableEntry, source: MutableEntry): void {\n target.calls += source.calls\n target.errors += source.errors\n target.concurrentCalls += source.concurrentCalls\n target.totalDurationMs += source.totalDurationMs\n target.maxDurationMs = Math.max(target.maxDurationMs, source.maxDurationMs)\n target.totalCpuUserMs += source.totalCpuUserMs\n target.totalCpuSystemMs += source.totalCpuSystemMs\n target.maxCpuMs = Math.max(target.maxCpuMs, source.maxCpuMs)\n target.totalHeapDeltaBytes += source.totalHeapDeltaBytes\n target.positiveHeapDeltaBytes += source.positiveHeapDeltaBytes\n target.maxHeapDeltaBytes = Math.max(target.maxHeapDeltaBytes, source.maxHeapDeltaBytes)\n target.totalRssDeltaBytes += source.totalRssDeltaBytes\n target.positiveRssDeltaBytes += source.positiveRssDeltaBytes\n target.maxRssDeltaBytes = Math.max(target.maxRssDeltaBytes, source.maxRssDeltaBytes)\n target.firstSeenAt = target.firstSeenAt < source.firstSeenAt ? target.firstSeenAt : source.firstSeenAt\n target.lastSeenAt = target.lastSeenAt > source.lastSeenAt ? target.lastSeenAt : source.lastSeenAt\n target.recentDurationsMs = trimRecentDurations([\n ...target.recentDurationsMs,\n ...(Array.isArray(source.recentDurationsMs) ? source.recentDurationsMs : []),\n ])\n}\n\nfunction cloneMutableEntry(entry: MutableEntry): MutableEntry {\n return {\n ...entry,\n recentDurationsMs: trimRecentDurations(Array.isArray(entry.recentDurationsMs) ? [...entry.recentDurationsMs] : []),\n }\n}\n\nfunction mergeMutableTimeBucket(targetBucket: MutableTimeBucket, sourceBucket: MutableTimeBucket): void {\n for (const sourceModule of sourceBucket.modules.values()) {\n if (!Array.isArray(sourceModule.recentDurationsMs)) sourceModule.recentDurationsMs = []\n if (!(sourceModule.operations instanceof Map)) sourceModule.operations = new Map()\n const targetModule = targetBucket.modules.get(sourceModule.moduleId)\n if (!targetModule) {\n targetBucket.modules.set(sourceModule.moduleId, {\n moduleId: sourceModule.moduleId,\n calls: sourceModule.calls,\n errors: sourceModule.errors,\n totalDurationMs: sourceModule.totalDurationMs,\n totalCpuMs: sourceModule.totalCpuMs,\n positiveHeapDeltaBytes: sourceModule.positiveHeapDeltaBytes,\n positiveRssDeltaBytes: sourceModule.positiveRssDeltaBytes,\n recentDurationsMs: trimRecentDurations([...sourceModule.recentDurationsMs]),\n operations: new Map(Array.from(sourceModule.operations.entries()).map(([key, entry]) => [key, cloneMutableEntry(entry)])),\n })\n continue\n }\n targetModule.calls += sourceModule.calls\n targetModule.errors += sourceModule.errors\n targetModule.totalDurationMs += sourceModule.totalDurationMs\n targetModule.totalCpuMs += sourceModule.totalCpuMs\n targetModule.positiveHeapDeltaBytes += sourceModule.positiveHeapDeltaBytes\n targetModule.positiveRssDeltaBytes += sourceModule.positiveRssDeltaBytes\n targetModule.recentDurationsMs = trimRecentDurations([\n ...targetModule.recentDurationsMs,\n ...sourceModule.recentDurationsMs,\n ])\n for (const [operationKey, sourceOperation] of sourceModule.operations) {\n const targetOperation = targetModule.operations.get(operationKey)\n if (!targetOperation) {\n targetModule.operations.set(operationKey, cloneMutableEntry(sourceOperation))\n continue\n }\n mergeMutableEntry(targetOperation, sourceOperation)\n }\n }\n}\n\nexport function resetModuleResourceUsage(): void {\n ;(globalThis as Record<string, unknown>)[GLOBAL_KEY] = {\n startedAt: new Date().toISOString(),\n entries: new Map(),\n buckets: new Map(),\n activeCalls: new Set(),\n lastSnapshotAt: 0,\n shutdownHookRegistered: false,\n migratedIntervalMs: moduleResourceUsageBucketIntervalMs(),\n } satisfies ResourceUsageState\n}\n\nexport function clearModuleResourceUsageData(): void {\n resetModuleResourceUsage()\n try {\n fs.rmSync(getSnapshotDir(), { recursive: true, force: true })\n } catch {\n // Telemetry is diagnostic only; clearing in-memory state is still useful if files cannot be removed.\n }\n}\n\nexport function isModuleResourceUsageEnabled(): boolean {\n return parseBooleanWithDefault(process.env.OM_MODULE_RESOURCE_USAGE, true)\n}\n\nfunction isSnapshotEnabled(): boolean {\n return parseBooleanWithDefault(process.env.OM_MODULE_RESOURCE_USAGE_SNAPSHOT, process.env.NODE_ENV !== 'test')\n}\n\nexport function inferModuleIdFromResourceId(resourceId: string | null | undefined): string | null {\n if (!resourceId) return null\n const trimmed = resourceId.trim()\n if (!trimmed) return null\n const colon = trimmed.indexOf(':')\n if (colon > 0) return trimmed.slice(0, colon)\n const dot = trimmed.indexOf('.')\n if (dot > 0) return trimmed.slice(0, dot)\n return null\n}\n\nfunction normalizeModuleId(input: ModuleResourceUsageInput): string | null {\n const explicit = input.moduleId?.trim()\n if (explicit) return explicit\n return inferModuleIdFromResourceId(input.resourceId) ?? inferModuleIdFromResourceId(input.operation)\n}\n\nfunction nowNs(): bigint {\n return process.hrtime.bigint()\n}\n\nfunction durationMs(startNs: bigint): number {\n const elapsed = nowNs() - startNs\n if (elapsed <= BigInt(0)) return 0\n return Number(elapsed) / NS_PER_MS\n}\n\nfunction round(value: number, precision = 100): number {\n if (!Number.isFinite(value)) return 0\n return Math.round(value * precision) / precision\n}\n\nfunction safeMemoryUsage(): NodeJS.MemoryUsage | null {\n try {\n return process.memoryUsage()\n } catch {\n return null\n }\n}\n\nfunction entryKey(input: Required<Pick<ModuleResourceUsageInput, 'surface' | 'operation'>> & { moduleId: string }): string {\n return `${input.moduleId}\\u0000${input.surface}\\u0000${input.operation}`\n}\n\nfunction getOrCreateEntry(input: ModuleResourceUsageInput & { moduleId: string }, timestamp: string): MutableEntry {\n const state = getState()\n const key = entryKey(input)\n const existing = state.entries.get(key)\n if (existing) return existing\n const created: MutableEntry = {\n moduleId: input.moduleId,\n surface: input.surface,\n operation: input.operation,\n resourceId: input.resourceId ?? null,\n calls: 0,\n errors: 0,\n concurrentCalls: 0,\n totalDurationMs: 0,\n maxDurationMs: 0,\n totalCpuUserMs: 0,\n totalCpuSystemMs: 0,\n maxCpuMs: 0,\n totalHeapDeltaBytes: 0,\n positiveHeapDeltaBytes: 0,\n maxHeapDeltaBytes: 0,\n totalRssDeltaBytes: 0,\n positiveRssDeltaBytes: 0,\n maxRssDeltaBytes: 0,\n firstSeenAt: timestamp,\n lastSeenAt: timestamp,\n recentDurationsMs: [],\n }\n state.entries.set(key, created)\n return created\n}\n\nfunction getOrCreateTimeBucket(state: ResourceUsageState, timestamp: string): MutableTimeBucket | null {\n const timestampMs = Date.parse(timestamp)\n if (!Number.isFinite(timestampMs)) return null\n const intervalMs = moduleResourceUsageBucketIntervalMs()\n const bucketStartMs = Math.floor(timestampMs / intervalMs) * intervalMs\n const key = String(bucketStartMs)\n const existing = state.buckets.get(key)\n if (existing) return existing\n const created: MutableTimeBucket = {\n bucketStartMs,\n bucketEndMs: bucketStartMs + intervalMs,\n modules: new Map(),\n }\n state.buckets.set(key, created)\n pruneTimeBuckets(state)\n return created\n}\n\nfunction getOrCreateTimeBucketModule(bucket: MutableTimeBucket, moduleId: string): MutableTimeBucketModule {\n const existing = bucket.modules.get(moduleId)\n if (existing) {\n if (!Array.isArray(existing.recentDurationsMs)) existing.recentDurationsMs = []\n if (!(existing.operations instanceof Map)) existing.operations = new Map()\n return existing\n }\n const created: MutableTimeBucketModule = {\n moduleId,\n calls: 0,\n errors: 0,\n totalDurationMs: 0,\n totalCpuMs: 0,\n positiveHeapDeltaBytes: 0,\n positiveRssDeltaBytes: 0,\n recentDurationsMs: [],\n operations: new Map(),\n }\n bucket.modules.set(moduleId, created)\n return created\n}\n\nfunction getOrCreateTimeBucketOperation(\n bucketModule: MutableTimeBucketModule,\n input: ModuleResourceUsageInput & { moduleId: string },\n timestamp: string,\n): MutableEntry {\n const key = entryKey(input)\n const existing = bucketModule.operations.get(key)\n if (existing) return existing\n const created: MutableEntry = {\n moduleId: input.moduleId,\n surface: input.surface,\n operation: input.operation,\n resourceId: input.resourceId ?? null,\n calls: 0,\n errors: 0,\n concurrentCalls: 0,\n totalDurationMs: 0,\n maxDurationMs: 0,\n totalCpuUserMs: 0,\n totalCpuSystemMs: 0,\n maxCpuMs: 0,\n totalHeapDeltaBytes: 0,\n positiveHeapDeltaBytes: 0,\n maxHeapDeltaBytes: 0,\n totalRssDeltaBytes: 0,\n positiveRssDeltaBytes: 0,\n maxRssDeltaBytes: 0,\n firstSeenAt: timestamp,\n lastSeenAt: timestamp,\n recentDurationsMs: [],\n }\n bucketModule.operations.set(key, created)\n return created\n}\n\nfunction pruneTimeBuckets(state: ResourceUsageState): void {\n const limit = moduleResourceUsageBucketLimit()\n if (state.buckets.size <= limit) return\n const keys = Array.from(state.buckets.entries())\n .sort((a, b) => a[1].bucketStartMs - b[1].bucketStartMs)\n .map(([key]) => key)\n while (state.buckets.size > limit) {\n const key = keys.shift()\n if (!key) break\n state.buckets.delete(key)\n }\n}\n\nfunction recordTimeBucket(input: ModuleResourceUsageInput & { moduleId: string }, timestamp: string, metrics: {\n durationMs: number\n cpuUserMs: number\n cpuSystemMs: number\n cpuMs: number\n heapDeltaBytes: number\n rssDeltaBytes: number\n status: 'ok' | 'error'\n concurrentTainted: boolean\n}): void {\n const state = getState()\n const bucket = getOrCreateTimeBucket(state, timestamp)\n if (!bucket) return\n const bucketModule = getOrCreateTimeBucketModule(bucket, input.moduleId)\n bucketModule.calls += 1\n if (metrics.status === 'error') bucketModule.errors += 1\n bucketModule.totalDurationMs += metrics.durationMs\n bucketModule.totalCpuMs += metrics.cpuMs\n bucketModule.positiveHeapDeltaBytes += Math.max(metrics.heapDeltaBytes, 0)\n bucketModule.positiveRssDeltaBytes += Math.max(metrics.rssDeltaBytes, 0)\n bucketModule.recentDurationsMs.push(metrics.durationMs)\n if (bucketModule.recentDurationsMs.length > DEFAULT_RECENT_SAMPLE_LIMIT) {\n bucketModule.recentDurationsMs.splice(0, bucketModule.recentDurationsMs.length - DEFAULT_RECENT_SAMPLE_LIMIT)\n }\n const operation = getOrCreateTimeBucketOperation(bucketModule, input, timestamp)\n operation.calls += 1\n if (metrics.status === 'error') operation.errors += 1\n if (metrics.concurrentTainted) operation.concurrentCalls += 1\n operation.totalDurationMs += metrics.durationMs\n operation.maxDurationMs = Math.max(operation.maxDurationMs, metrics.durationMs)\n operation.totalCpuUserMs += metrics.cpuUserMs\n operation.totalCpuSystemMs += metrics.cpuSystemMs\n operation.maxCpuMs = Math.max(operation.maxCpuMs, metrics.cpuMs)\n operation.totalHeapDeltaBytes += metrics.heapDeltaBytes\n operation.positiveHeapDeltaBytes += Math.max(metrics.heapDeltaBytes, 0)\n operation.maxHeapDeltaBytes = Math.max(operation.maxHeapDeltaBytes, metrics.heapDeltaBytes)\n operation.totalRssDeltaBytes += metrics.rssDeltaBytes\n operation.positiveRssDeltaBytes += Math.max(metrics.rssDeltaBytes, 0)\n operation.maxRssDeltaBytes = Math.max(operation.maxRssDeltaBytes, metrics.rssDeltaBytes)\n operation.lastSeenAt = timestamp\n operation.recentDurationsMs.push(metrics.durationMs)\n if (operation.recentDurationsMs.length > DEFAULT_RECENT_SAMPLE_LIMIT) {\n operation.recentDurationsMs.splice(0, operation.recentDurationsMs.length - DEFAULT_RECENT_SAMPLE_LIMIT)\n }\n}\n\nfunction recordModuleResourceUsage(input: ModuleResourceUsageInput, metrics: {\n durationMs: number\n cpuUserMicros: number\n cpuSystemMicros: number\n heapDeltaBytes: number\n rssDeltaBytes: number\n status: 'ok' | 'error'\n concurrentTainted: boolean\n}): void {\n const moduleId = normalizeModuleId(input)\n if (!moduleId) return\n const timestamp = new Date().toISOString()\n const entry = getOrCreateEntry({ ...input, moduleId }, timestamp)\n const cpuUserMs = metrics.cpuUserMicros / MICROS_PER_MS\n const cpuSystemMs = metrics.cpuSystemMicros / MICROS_PER_MS\n const cpuMs = cpuUserMs + cpuSystemMs\n entry.calls += 1\n if (metrics.status === 'error') entry.errors += 1\n if (metrics.concurrentTainted) entry.concurrentCalls += 1\n entry.totalDurationMs += metrics.durationMs\n entry.maxDurationMs = Math.max(entry.maxDurationMs, metrics.durationMs)\n entry.totalCpuUserMs += cpuUserMs\n entry.totalCpuSystemMs += cpuSystemMs\n entry.maxCpuMs = Math.max(entry.maxCpuMs, cpuMs)\n entry.totalHeapDeltaBytes += metrics.heapDeltaBytes\n entry.positiveHeapDeltaBytes += Math.max(metrics.heapDeltaBytes, 0)\n entry.maxHeapDeltaBytes = Math.max(entry.maxHeapDeltaBytes, metrics.heapDeltaBytes)\n entry.totalRssDeltaBytes += metrics.rssDeltaBytes\n entry.positiveRssDeltaBytes += Math.max(metrics.rssDeltaBytes, 0)\n entry.maxRssDeltaBytes = Math.max(entry.maxRssDeltaBytes, metrics.rssDeltaBytes)\n entry.lastSeenAt = timestamp\n entry.recentDurationsMs.push(metrics.durationMs)\n if (entry.recentDurationsMs.length > DEFAULT_RECENT_SAMPLE_LIMIT) {\n entry.recentDurationsMs.splice(0, entry.recentDurationsMs.length - DEFAULT_RECENT_SAMPLE_LIMIT)\n }\n recordTimeBucket({ ...input, moduleId }, timestamp, {\n durationMs: metrics.durationMs,\n cpuUserMs,\n cpuSystemMs,\n cpuMs,\n heapDeltaBytes: metrics.heapDeltaBytes,\n rssDeltaBytes: metrics.rssDeltaBytes,\n status: metrics.status,\n concurrentTainted: metrics.concurrentTainted,\n })\n flushModuleResourceUsageSnapshot(false)\n}\n\nexport async function withModuleResourceUsage<T>(\n input: ModuleResourceUsageInput,\n fn: () => Promise<T> | T,\n): Promise<T> {\n if (!isModuleResourceUsageEnabled() || !normalizeModuleId(input)) {\n return Promise.resolve(fn())\n }\n const state = getState()\n // Mark this call tainted if another tracked call is already in flight, and mark every\n // currently in-flight call tainted too, since process.cpuUsage()/process.memoryUsage() can't\n // separate their concurrent work from ours (see ModuleResourceUsageEntry.concurrentCalls).\n const token: ActiveCallToken = { tainted: state.activeCalls.size > 0 }\n if (token.tainted) {\n for (const other of state.activeCalls) other.tainted = true\n }\n state.activeCalls.add(token)\n const startNs = nowNs()\n const startCpu = process.cpuUsage()\n const startMemory = safeMemoryUsage()\n // Recording must never override the wrapped call's real outcome: `fn()` is awaited in its own\n // try/catch, and the bookkeeping below always runs in a try/catch of its own so a bug in the\n // telemetry path can only be lost silently, never surface as a fake success/failure for `fn()`.\n let result: T\n try {\n result = await Promise.resolve(fn())\n } catch (error) {\n state.activeCalls.delete(token)\n try {\n const cpu = process.cpuUsage(startCpu)\n const endMemory = safeMemoryUsage()\n recordModuleResourceUsage(input, {\n durationMs: durationMs(startNs),\n cpuUserMicros: cpu.user,\n cpuSystemMicros: cpu.system,\n heapDeltaBytes: endMemory && startMemory ? endMemory.heapUsed - startMemory.heapUsed : 0,\n rssDeltaBytes: endMemory && startMemory ? endMemory.rss - startMemory.rss : 0,\n status: 'error',\n concurrentTainted: token.tainted,\n })\n } catch {\n // Telemetry bookkeeping is diagnostic only; never mask the real error from the wrapped call.\n }\n throw error\n }\n state.activeCalls.delete(token)\n try {\n const cpu = process.cpuUsage(startCpu)\n const endMemory = safeMemoryUsage()\n recordModuleResourceUsage(input, {\n durationMs: durationMs(startNs),\n cpuUserMicros: cpu.user,\n cpuSystemMicros: cpu.system,\n heapDeltaBytes: endMemory && startMemory ? endMemory.heapUsed - startMemory.heapUsed : 0,\n rssDeltaBytes: endMemory && startMemory ? endMemory.rss - startMemory.rss : 0,\n status: 'ok',\n concurrentTainted: token.tainted,\n })\n } catch {\n // Telemetry bookkeeping is diagnostic only; never mask the real result from the wrapped call.\n }\n return result\n}\n\nfunction percentile(values: number[], p: number): number {\n if (!values.length) return 0\n const sorted = values.slice().sort((a, b) => a - b)\n const index = Math.min(sorted.length - 1, Math.max(0, Math.ceil((p / 100) * sorted.length) - 1))\n return sorted[index]\n}\n\nfunction serializeEntry(entry: MutableEntry): ModuleResourceUsageEntry {\n return {\n moduleId: entry.moduleId,\n surface: entry.surface,\n operation: entry.operation,\n resourceId: entry.resourceId,\n calls: entry.calls,\n errors: entry.errors,\n concurrentCalls: entry.concurrentCalls,\n totalDurationMs: round(entry.totalDurationMs),\n maxDurationMs: round(entry.maxDurationMs),\n p95DurationMs: round(percentile(entry.recentDurationsMs, 95)),\n totalCpuUserMs: round(entry.totalCpuUserMs),\n totalCpuSystemMs: round(entry.totalCpuSystemMs),\n maxCpuMs: round(entry.maxCpuMs),\n totalHeapDeltaBytes: Math.round(entry.totalHeapDeltaBytes),\n positiveHeapDeltaBytes: Math.round(entry.positiveHeapDeltaBytes),\n maxHeapDeltaBytes: Math.round(entry.maxHeapDeltaBytes),\n totalRssDeltaBytes: Math.round(entry.totalRssDeltaBytes),\n positiveRssDeltaBytes: Math.round(entry.positiveRssDeltaBytes),\n maxRssDeltaBytes: Math.round(entry.maxRssDeltaBytes),\n firstSeenAt: entry.firstSeenAt,\n lastSeenAt: entry.lastSeenAt,\n }\n}\n\nfunction timeBucketTotals(modules: ModuleResourceUsageTimeBucketModule[]): ModuleResourceUsageTimeBucket['totals'] {\n return {\n modules: modules.length,\n calls: modules.reduce((sum, module) => sum + module.calls, 0),\n errors: modules.reduce((sum, module) => sum + module.errors, 0),\n totalDurationMs: round(modules.reduce((sum, module) => sum + module.totalDurationMs, 0)),\n totalCpuMs: round(modules.reduce((sum, module) => sum + module.totalCpuMs, 0)),\n positiveHeapDeltaBytes: modules.reduce((sum, module) => sum + module.positiveHeapDeltaBytes, 0),\n positiveRssDeltaBytes: modules.reduce((sum, module) => sum + module.positiveRssDeltaBytes, 0),\n }\n}\n\nfunction surfaceSummaries(entries: ModuleResourceUsageEntry[]): ModuleResourceUsageSurfaceSummary[] {\n const surfaces = new Map<ModuleResourceSurface, ModuleResourceUsageEntry[]>()\n for (const entry of entries) {\n const list = surfaces.get(entry.surface) ?? []\n list.push(entry)\n surfaces.set(entry.surface, list)\n }\n return Array.from(surfaces.entries()).map(([surface, surfaceEntries]) => ({\n surface,\n calls: surfaceEntries.reduce((sum, entry) => sum + entry.calls, 0),\n errors: surfaceEntries.reduce((sum, entry) => sum + entry.errors, 0),\n totalDurationMs: round(surfaceEntries.reduce((sum, entry) => sum + entry.totalDurationMs, 0)),\n p95DurationMs: round(Math.max(...surfaceEntries.map((entry) => entry.p95DurationMs), 0)),\n totalCpuMs: round(surfaceEntries.reduce((sum, entry) => sum + entry.totalCpuUserMs + entry.totalCpuSystemMs, 0)),\n positiveHeapDeltaBytes: surfaceEntries.reduce((sum, entry) => sum + entry.positiveHeapDeltaBytes, 0),\n positiveRssDeltaBytes: surfaceEntries.reduce((sum, entry) => sum + entry.positiveRssDeltaBytes, 0),\n })).sort((a, b) => b.totalCpuMs - a.totalCpuMs || b.totalDurationMs - a.totalDurationMs)\n}\n\nfunction moduleResourceUsageBucketStage(bucketStartMs: number, bucketEndMs: number, startedAtMs: number): ModuleResourceUsageTimeBucket['stage'] {\n if (!Number.isFinite(bucketStartMs) || !Number.isFinite(bucketEndMs) || !Number.isFinite(startedAtMs)) return 'running'\n return bucketEndMs > startedAtMs && bucketStartMs < startedAtMs + MODULE_RESOURCE_USAGE_STARTUP_STAGE_MS\n ? 'startup'\n : 'running'\n}\n\nfunction serializeTimeBucket(bucket: MutableTimeBucket, nowMs: number, startedAtMs: number): ModuleResourceUsageTimeBucket {\n const thresholds = getModuleResourceUsageThresholds()\n const modules = Array.from(bucket.modules.values()).map((module) => ({\n module,\n operations: module.operations instanceof Map\n ? Array.from(module.operations.values()).map(serializeEntry)\n : [],\n })).map(({ module, operations }) => {\n const summaryBase = {\n moduleId: module.moduleId,\n calls: module.calls,\n errors: module.errors,\n totalDurationMs: round(module.totalDurationMs),\n p95DurationMs: round(percentile(module.recentDurationsMs, 95)),\n totalCpuMs: round(module.totalCpuMs),\n positiveHeapDeltaBytes: Math.round(module.positiveHeapDeltaBytes),\n positiveRssDeltaBytes: Math.round(module.positiveRssDeltaBytes),\n surfaces: surfaceSummaries(operations),\n topOperations: operations\n .slice()\n .sort((a, b) => (b.totalCpuUserMs + b.totalCpuSystemMs) - (a.totalCpuUserMs + a.totalCpuSystemMs) || b.totalDurationMs - a.totalDurationMs)\n .slice(0, DEFAULT_TOP_OPERATIONS_LIMIT),\n }\n return {\n ...summaryBase,\n candidateReasons: buildCandidateReasons(summaryBase, thresholds),\n }\n }).sort((a, b) =>\n b.candidateReasons.length - a.candidateReasons.length\n || b.totalCpuMs - a.totalCpuMs\n || b.positiveRssDeltaBytes - a.positiveRssDeltaBytes\n || b.calls - a.calls\n )\n return {\n bucketStart: new Date(bucket.bucketStartMs).toISOString(),\n bucketEnd: new Date(bucket.bucketEndMs).toISOString(),\n bucketIntervalMs: bucket.bucketEndMs - bucket.bucketStartMs,\n stage: moduleResourceUsageBucketStage(bucket.bucketStartMs, bucket.bucketEndMs, startedAtMs),\n partial: bucket.bucketStartMs < startedAtMs || nowMs < bucket.bucketEndMs,\n totals: timeBucketTotals(modules),\n modules,\n }\n}\n\nfunction serializeTimeBuckets(state: ResourceUsageState): ModuleResourceUsageTimeBucket[] {\n const nowMs = Date.now()\n const startedAtMs = Date.parse(state.startedAt)\n return Array.from(state.buckets.values())\n .sort((a, b) => a.bucketStartMs - b.bucketStartMs)\n .map((bucket) => serializeTimeBucket(bucket, nowMs, Number.isFinite(startedAtMs) ? startedAtMs : nowMs))\n}\n\nfunction getSnapshotDir(): string {\n const configured = process.env.OM_MODULE_RESOURCE_USAGE_DIR?.trim()\n return configured ? path.resolve(configured) : path.resolve(process.cwd(), '.mercato/module-resource-usage')\n}\n\nfunction getSnapshotPath(): string {\n return path.join(getSnapshotDir(), `process-${process.pid}.json`)\n}\n\nfunction registerSnapshotShutdownHook(state: ResourceUsageState): void {\n if (state.shutdownHookRegistered) return\n const flush = () => {\n try {\n flushModuleResourceUsageSnapshot(true)\n } catch {\n // Best-effort diagnostic snapshot; never block process shutdown.\n }\n }\n process.once('beforeExit', flush)\n state.shutdownHookRegistered = true\n}\n\n// Fire-and-forget: nothing in this process depends on the write completing (readers always\n// skip their own pid's file \u2014 see readSnapshotPayloads), so there's no reason for the write to\n// block the tracked call, event dispatch, or report request that triggered it. This mirrors the\n// existing fire-and-forget queue-close pattern in registerProducerShutdownHook (bus.ts): Node's\n// event loop stays alive for the pending promise, so even the beforeExit-triggered flush below\n// still completes before the process actually exits, as long as nothing calls process.exit()\n// first.\nlet snapshotWriteSequence = 0\n\n// Writes atomically: serialize to a unique temp file, then rename onto the final path. rename is\n// atomic on POSIX, so a concurrent reader (this process's report, another process's directory scan,\n// or a test) always observes either the previous complete file or the new complete file \u2014 never the\n// empty/partial content window that a direct writeFile to snapshotPath would briefly expose.\nfunction writeSnapshotFileAsync(dir: string, snapshotPath: string, payload: unknown): void {\n const tempPath = `${snapshotPath}.${process.pid}.${snapshotWriteSequence++}.tmp`\n fs.promises.mkdir(dir, { recursive: true })\n .then(() => fs.promises.writeFile(tempPath, JSON.stringify(payload, null, 2)))\n .then(() => fs.promises.rename(tempPath, snapshotPath))\n .catch(() => {\n // Snapshot files are diagnostic only; in-memory reporting still works. Best-effort cleanup of\n // the temp file if the rename never happened; ignore if it is already gone.\n fs.promises.unlink(tempPath).catch(() => {})\n })\n}\n\nfunction flushModuleResourceUsageSnapshot(force: boolean): void {\n if (!isSnapshotEnabled()) return\n const state = getState()\n const now = Date.now()\n if (!force && now - state.lastSnapshotAt < SNAPSHOT_THROTTLE_MS) return\n state.lastSnapshotAt = now\n try {\n const payload = {\n pid: process.pid,\n generatedAt: new Date().toISOString(),\n startedAt: state.startedAt,\n entries: Array.from(state.entries.values()).map(serializeEntry),\n buckets: serializeTimeBuckets(state),\n }\n writeSnapshotFileAsync(getSnapshotDir(), getSnapshotPath(), payload)\n } catch {\n // Snapshot files are diagnostic only; in-memory reporting still works.\n }\n}\n\ntype SnapshotPayload = {\n pid?: unknown\n generatedAt?: unknown\n startedAt?: unknown\n entries?: unknown\n buckets?: unknown\n}\n\nfunction isFreshSnapshot(payload: SnapshotPayload): boolean {\n if (typeof payload.generatedAt !== 'string') return false\n const generatedAt = Date.parse(payload.generatedAt)\n return Number.isFinite(generatedAt) && Date.now() - generatedAt <= SNAPSHOT_MAX_AGE_MS\n}\n\n// Reads every other process's snapshot file exactly once. Both readSnapshotEntries and\n// readSnapshotBuckets used to each force a flush and independently re-scan/re-parse the whole\n// directory; callers now do that single scan once (see getModuleResourceUsageReport) and pass\n// the result to both.\n// Restarts/redeploys/autoscaling leave one snapshot file behind per historical process. Prune\n// stale ones opportunistically here (best-effort, never blocks the read) so the directory doesn't\n// grow without bound in a long-running deployment.\nfunction pruneStaleSnapshotFile(filePath: string): void {\n fs.promises.unlink(filePath).catch(() => {\n // Snapshot files are diagnostic only; leave it for a later pass if it can't be removed now.\n })\n}\n\nfunction readSnapshotPayloads(): SnapshotPayload[] {\n if (!isSnapshotEnabled()) return []\n const dir = getSnapshotDir()\n if (!fs.existsSync(dir)) return []\n const payloads: SnapshotPayload[] = []\n for (const file of fs.readdirSync(dir)) {\n if (!file.endsWith('.json')) continue\n const filePath = path.join(dir, file)\n try {\n const payload = JSON.parse(fs.readFileSync(filePath, 'utf8')) as SnapshotPayload\n if (payload.pid === process.pid) continue\n if (!isFreshSnapshot(payload)) {\n pruneStaleSnapshotFile(filePath)\n continue\n }\n payloads.push(payload)\n } catch {\n // Ignore malformed or concurrently-written snapshots.\n }\n }\n return payloads\n}\n\nfunction readSnapshotEntries(payloads: SnapshotPayload[]): ModuleResourceUsageEntry[] {\n const entries: ModuleResourceUsageEntry[] = []\n for (const payload of payloads) {\n if (!Array.isArray(payload.entries)) continue\n for (const entry of payload.entries) {\n if (!entry || typeof entry !== 'object') continue\n const candidate = entry as Partial<ModuleResourceUsageEntry>\n if (typeof candidate.moduleId !== 'string' || typeof candidate.operation !== 'string') continue\n if (!candidate.surface) continue\n entries.push(candidate as ModuleResourceUsageEntry)\n }\n }\n return entries\n}\n\nfunction readSnapshotBuckets(payloads: SnapshotPayload[]): ModuleResourceUsageTimeBucket[] {\n const buckets: ModuleResourceUsageTimeBucket[] = []\n for (const payload of payloads) {\n try {\n if (!Array.isArray(payload.buckets)) continue\n for (const bucket of payload.buckets) {\n if (!bucket || typeof bucket !== 'object') continue\n const candidate = bucket as Partial<ModuleResourceUsageTimeBucket>\n if (typeof candidate.bucketStart !== 'string' || typeof candidate.bucketEnd !== 'string') continue\n if (!Array.isArray(candidate.modules)) continue\n const modules = (candidate.modules as Partial<ModuleResourceUsageTimeBucketModule>[]).map(normalizeTimeBucketModule)\n buckets.push({\n bucketStart: candidate.bucketStart,\n bucketEnd: candidate.bucketEnd,\n bucketIntervalMs: normalizeBucketIntervalMs(candidate),\n stage: candidate.stage === 'startup' || candidate.stage === 'running'\n ? candidate.stage\n : moduleResourceUsageBucketStage(\n Date.parse(candidate.bucketStart),\n Date.parse(candidate.bucketEnd),\n typeof payload.startedAt === 'string' ? Date.parse(payload.startedAt) : Date.parse(candidate.bucketStart),\n ),\n partial: candidate.partial === true,\n totals: candidate.totals ?? timeBucketTotals(modules),\n modules,\n })\n }\n } catch {\n // Ignore malformed or concurrently-written snapshots.\n }\n }\n return buckets\n}\n\nfunction normalizeBucketIntervalMs(bucket: Partial<ModuleResourceUsageTimeBucket>): number {\n if (Number.isFinite(bucket.bucketIntervalMs) && Number(bucket.bucketIntervalMs) > 0) {\n return Number(bucket.bucketIntervalMs)\n }\n const bucketStartMs = typeof bucket.bucketStart === 'string' ? Date.parse(bucket.bucketStart) : NaN\n const bucketEndMs = typeof bucket.bucketEnd === 'string' ? Date.parse(bucket.bucketEnd) : NaN\n const inferred = bucketEndMs - bucketStartMs\n return Number.isFinite(inferred) && inferred > 0 ? inferred : moduleResourceUsageBucketIntervalMs()\n}\n\nfunction normalizeTimeBucketModule(module: Partial<ModuleResourceUsageTimeBucketModule>): ModuleResourceUsageTimeBucketModule {\n return {\n moduleId: typeof module.moduleId === 'string' ? module.moduleId : 'unknown',\n calls: Number.isFinite(module.calls) ? Number(module.calls) : 0,\n errors: Number.isFinite(module.errors) ? Number(module.errors) : 0,\n totalDurationMs: Number.isFinite(module.totalDurationMs) ? Number(module.totalDurationMs) : 0,\n p95DurationMs: Number.isFinite(module.p95DurationMs) ? Number(module.p95DurationMs) : 0,\n totalCpuMs: Number.isFinite(module.totalCpuMs) ? Number(module.totalCpuMs) : 0,\n positiveHeapDeltaBytes: Number.isFinite(module.positiveHeapDeltaBytes) ? Number(module.positiveHeapDeltaBytes) : 0,\n positiveRssDeltaBytes: Number.isFinite(module.positiveRssDeltaBytes) ? Number(module.positiveRssDeltaBytes) : 0,\n surfaces: Array.isArray(module.surfaces) ? module.surfaces : [],\n topOperations: Array.isArray(module.topOperations) ? module.topOperations : [],\n candidateReasons: Array.isArray(module.candidateReasons) ? module.candidateReasons : [],\n }\n}\n\nfunction mergeEntries(entries: ModuleResourceUsageEntry[]): ModuleResourceUsageEntry[] {\n const merged = new Map<string, ModuleResourceUsageEntry>()\n for (const entry of entries) {\n const key = entryKey({ moduleId: entry.moduleId, surface: entry.surface, operation: entry.operation })\n const existing = merged.get(key)\n if (!existing) {\n merged.set(key, {\n ...entry,\n // Snapshot files written by an older process before concurrentCalls existed lack it.\n concurrentCalls: Number.isFinite(entry.concurrentCalls) ? entry.concurrentCalls : 0,\n })\n continue\n }\n existing.calls += entry.calls\n existing.errors += entry.errors\n existing.concurrentCalls += Number.isFinite(entry.concurrentCalls) ? entry.concurrentCalls : 0\n existing.totalDurationMs = round(existing.totalDurationMs + entry.totalDurationMs)\n existing.maxDurationMs = Math.max(existing.maxDurationMs, entry.maxDurationMs)\n existing.p95DurationMs = Math.max(existing.p95DurationMs, entry.p95DurationMs)\n existing.totalCpuUserMs = round(existing.totalCpuUserMs + entry.totalCpuUserMs)\n existing.totalCpuSystemMs = round(existing.totalCpuSystemMs + entry.totalCpuSystemMs)\n existing.maxCpuMs = Math.max(existing.maxCpuMs, entry.maxCpuMs)\n existing.totalHeapDeltaBytes += entry.totalHeapDeltaBytes\n existing.positiveHeapDeltaBytes += entry.positiveHeapDeltaBytes\n existing.maxHeapDeltaBytes = Math.max(existing.maxHeapDeltaBytes, entry.maxHeapDeltaBytes)\n existing.totalRssDeltaBytes += entry.totalRssDeltaBytes\n existing.positiveRssDeltaBytes += entry.positiveRssDeltaBytes\n existing.maxRssDeltaBytes = Math.max(existing.maxRssDeltaBytes, entry.maxRssDeltaBytes)\n existing.firstSeenAt = existing.firstSeenAt < entry.firstSeenAt ? existing.firstSeenAt : entry.firstSeenAt\n existing.lastSeenAt = existing.lastSeenAt > entry.lastSeenAt ? existing.lastSeenAt : entry.lastSeenAt\n }\n return Array.from(merged.values())\n}\n\nfunction mergeSurfaceSummaries(surfaces: ModuleResourceUsageSurfaceSummary[]): ModuleResourceUsageSurfaceSummary[] {\n const merged = new Map<ModuleResourceSurface, ModuleResourceUsageSurfaceSummary>()\n for (const surface of surfaces) {\n const existing = merged.get(surface.surface)\n if (!existing) {\n merged.set(surface.surface, { ...surface })\n continue\n }\n existing.calls += surface.calls\n existing.errors += surface.errors\n existing.totalDurationMs = round(existing.totalDurationMs + surface.totalDurationMs)\n existing.p95DurationMs = Math.max(existing.p95DurationMs, surface.p95DurationMs)\n existing.totalCpuMs = round(existing.totalCpuMs + surface.totalCpuMs)\n existing.positiveHeapDeltaBytes += surface.positiveHeapDeltaBytes\n existing.positiveRssDeltaBytes += surface.positiveRssDeltaBytes\n }\n return Array.from(merged.values()).sort((a, b) => b.totalCpuMs - a.totalCpuMs || b.totalDurationMs - a.totalDurationMs)\n}\n\nfunction mergeOperationEntries(entries: ModuleResourceUsageEntry[]): ModuleResourceUsageEntry[] {\n return mergeEntries(entries)\n .sort((a, b) => (b.totalCpuUserMs + b.totalCpuSystemMs) - (a.totalCpuUserMs + a.totalCpuSystemMs) || b.totalDurationMs - a.totalDurationMs)\n .slice(0, DEFAULT_TOP_OPERATIONS_LIMIT)\n}\n\nfunction mergeTimeBuckets(buckets: ModuleResourceUsageTimeBucket[]): ModuleResourceUsageTimeBucket[] {\n const merged = new Map<string, ModuleResourceUsageTimeBucket>()\n const thresholds = getModuleResourceUsageThresholds()\n for (const bucket of buckets) {\n const bucketStartMs = Date.parse(bucket.bucketStart)\n if (!Number.isFinite(bucketStartMs)) continue\n const bucketIntervalMs = normalizeBucketIntervalMs(bucket)\n const key = `${bucket.bucketStart}\\u0000${bucketIntervalMs}`\n const existing = merged.get(key)\n if (!existing) {\n const modules = bucket.modules.map((module) => {\n const normalizedModule = normalizeTimeBucketModule(module)\n return {\n ...normalizedModule,\n candidateReasons: buildCandidateReasons(normalizedModule, thresholds),\n }\n })\n merged.set(key, {\n bucketStart: bucket.bucketStart,\n bucketEnd: bucket.bucketEnd,\n bucketIntervalMs,\n stage: bucket.stage,\n partial: bucket.partial,\n totals: timeBucketTotals(modules),\n modules,\n })\n continue\n }\n existing.partial = existing.partial || bucket.partial\n if (existing.stage !== 'startup') existing.stage = bucket.stage\n const modulesById = new Map(existing.modules.map((module) => [module.moduleId, module]))\n for (const module of bucket.modules) {\n const normalizedModule = normalizeTimeBucketModule(module)\n const current = modulesById.get(normalizedModule.moduleId)\n if (!current) {\n const created = {\n ...normalizedModule,\n candidateReasons: buildCandidateReasons(normalizedModule, thresholds),\n }\n existing.modules.push(created)\n modulesById.set(normalizedModule.moduleId, created)\n continue\n }\n current.calls += normalizedModule.calls\n current.errors += normalizedModule.errors\n current.totalDurationMs = round(current.totalDurationMs + normalizedModule.totalDurationMs)\n current.p95DurationMs = Math.max(current.p95DurationMs, normalizedModule.p95DurationMs)\n current.totalCpuMs = round(current.totalCpuMs + normalizedModule.totalCpuMs)\n current.positiveHeapDeltaBytes += normalizedModule.positiveHeapDeltaBytes\n current.positiveRssDeltaBytes += normalizedModule.positiveRssDeltaBytes\n current.surfaces = mergeSurfaceSummaries([...current.surfaces, ...normalizedModule.surfaces])\n current.topOperations = mergeOperationEntries([...current.topOperations, ...normalizedModule.topOperations])\n current.candidateReasons = buildCandidateReasons(current, thresholds)\n }\n existing.modules.sort((a, b) =>\n b.totalCpuMs - a.totalCpuMs\n || b.positiveRssDeltaBytes - a.positiveRssDeltaBytes\n || b.calls - a.calls\n )\n existing.totals = timeBucketTotals(existing.modules)\n }\n return Array.from(merged.values())\n .sort((a, b) => a.bucketStart.localeCompare(b.bucketStart))\n .slice(-moduleResourceUsageBucketLimit())\n}\n\nfunction readNumberEnv(name: string, fallback: number): number {\n return parseNumberWithDefault(process.env[name], fallback, { min: 0 })\n}\n\nfunction moduleResourceUsageBucketIntervalMs(): number {\n return MODULE_RESOURCE_USAGE_BUCKET_INTERVAL_MS\n}\n\nfunction moduleResourceUsageBucketLimit(): number {\n return Math.max(1, Math.ceil(TIME_BUCKET_RETENTION_MS / moduleResourceUsageBucketIntervalMs()))\n}\n\nexport function getModuleResourceUsageThresholds(): ModuleResourceUsageThresholds {\n return {\n p95DurationMs: readNumberEnv('OM_MODULE_RESOURCE_HEAVY_P95_MS', 5_000),\n cpuMs: readNumberEnv('OM_MODULE_RESOURCE_HEAVY_CPU_MS', 25_000),\n positiveHeapDeltaBytes: readNumberEnv('OM_MODULE_RESOURCE_HEAVY_HEAP_BYTES', 250 * 1024 * 1024),\n positiveRssDeltaBytes: readNumberEnv('OM_MODULE_RESOURCE_HEAVY_RSS_BYTES', 250 * 1024 * 1024),\n errors: readNumberEnv('OM_MODULE_RESOURCE_HEAVY_ERRORS', 10),\n }\n}\n\nfunction buildCandidateReasons(\n summary: Omit<ModuleResourceUsageModuleSummary, 'candidateReasons'>,\n thresholds: ModuleResourceUsageThresholds,\n): string[] {\n const reasons: string[] = []\n if (summary.p95DurationMs >= thresholds.p95DurationMs) reasons.push('p95_duration')\n if (summary.totalCpuMs >= thresholds.cpuMs) reasons.push('cpu')\n if (summary.positiveHeapDeltaBytes >= thresholds.positiveHeapDeltaBytes) reasons.push('heap_allocations')\n if (summary.positiveRssDeltaBytes >= thresholds.positiveRssDeltaBytes) reasons.push('rss_growth')\n if (summary.errors >= thresholds.errors) reasons.push('errors')\n return reasons\n}\n\nexport function getModuleResourceUsageReport(): ModuleResourceUsageReport {\n const state = getState()\n // Force our own snapshot to disk once (fire-and-forget \u2014 see writeSnapshotFileAsync) so other\n // processes viewing the report around the same time see fresh data, then read every other\n // process's snapshot file exactly once and derive both entries and buckets from that one scan.\n flushModuleResourceUsageSnapshot(true)\n const snapshotPayloads = readSnapshotPayloads()\n const entries = mergeEntries([\n ...Array.from(state.entries.values()).map(serializeEntry),\n ...readSnapshotEntries(snapshotPayloads),\n ])\n const buckets = mergeTimeBuckets([\n ...serializeTimeBuckets(state),\n ...readSnapshotBuckets(snapshotPayloads),\n ])\n const thresholds = getModuleResourceUsageThresholds()\n const modules = new Map<string, ModuleResourceUsageEntry[]>()\n for (const entry of entries) {\n const list = modules.get(entry.moduleId) ?? []\n list.push(entry)\n modules.set(entry.moduleId, list)\n }\n\n const moduleSummaries: ModuleResourceUsageModuleSummary[] = []\n for (const [moduleId, moduleEntries] of modules) {\n const surfaces = new Map<ModuleResourceSurface, ModuleResourceUsageEntry[]>()\n for (const entry of moduleEntries) {\n const list = surfaces.get(entry.surface) ?? []\n list.push(entry)\n surfaces.set(entry.surface, list)\n }\n const totalDurationMs = moduleEntries.reduce((sum, entry) => sum + entry.totalDurationMs, 0)\n const totalCpuMs = moduleEntries.reduce((sum, entry) => sum + entry.totalCpuUserMs + entry.totalCpuSystemMs, 0)\n const summaryBase = {\n moduleId,\n calls: moduleEntries.reduce((sum, entry) => sum + entry.calls, 0),\n errors: moduleEntries.reduce((sum, entry) => sum + entry.errors, 0),\n totalDurationMs: round(totalDurationMs),\n p95DurationMs: round(Math.max(...moduleEntries.map((entry) => entry.p95DurationMs), 0)),\n totalCpuMs: round(totalCpuMs),\n positiveHeapDeltaBytes: moduleEntries.reduce((sum, entry) => sum + entry.positiveHeapDeltaBytes, 0),\n positiveRssDeltaBytes: moduleEntries.reduce((sum, entry) => sum + entry.positiveRssDeltaBytes, 0),\n surfaces: Array.from(surfaces.entries()).map(([surface, surfaceEntries]) => ({\n surface,\n calls: surfaceEntries.reduce((sum, entry) => sum + entry.calls, 0),\n errors: surfaceEntries.reduce((sum, entry) => sum + entry.errors, 0),\n totalDurationMs: round(surfaceEntries.reduce((sum, entry) => sum + entry.totalDurationMs, 0)),\n p95DurationMs: round(Math.max(...surfaceEntries.map((entry) => entry.p95DurationMs), 0)),\n totalCpuMs: round(surfaceEntries.reduce((sum, entry) => sum + entry.totalCpuUserMs + entry.totalCpuSystemMs, 0)),\n positiveHeapDeltaBytes: surfaceEntries.reduce((sum, entry) => sum + entry.positiveHeapDeltaBytes, 0),\n positiveRssDeltaBytes: surfaceEntries.reduce((sum, entry) => sum + entry.positiveRssDeltaBytes, 0),\n })).sort((a, b) => b.totalCpuMs - a.totalCpuMs || b.totalDurationMs - a.totalDurationMs),\n topOperations: moduleEntries\n .slice()\n .sort((a, b) => (b.totalCpuUserMs + b.totalCpuSystemMs) - (a.totalCpuUserMs + a.totalCpuSystemMs) || b.totalDurationMs - a.totalDurationMs)\n .slice(0, DEFAULT_TOP_OPERATIONS_LIMIT),\n }\n moduleSummaries.push({\n ...summaryBase,\n candidateReasons: buildCandidateReasons(summaryBase, thresholds),\n })\n }\n\n const sortedModules = moduleSummaries.sort((a, b) =>\n b.candidateReasons.length - a.candidateReasons.length\n || b.totalCpuMs - a.totalCpuMs\n || b.totalDurationMs - a.totalDurationMs\n )\n\n return {\n generatedAt: new Date().toISOString(),\n startedAt: state.startedAt,\n enabled: isModuleResourceUsageEnabled(),\n bucketIntervalMs: moduleResourceUsageBucketIntervalMs(),\n totals: {\n modules: sortedModules.length,\n operations: entries.length,\n calls: entries.reduce((sum, entry) => sum + entry.calls, 0),\n errors: entries.reduce((sum, entry) => sum + entry.errors, 0),\n totalDurationMs: round(entries.reduce((sum, entry) => sum + entry.totalDurationMs, 0)),\n totalCpuMs: round(entries.reduce((sum, entry) => sum + entry.totalCpuUserMs + entry.totalCpuSystemMs, 0)),\n positiveHeapDeltaBytes: entries.reduce((sum, entry) => sum + entry.positiveHeapDeltaBytes, 0),\n positiveRssDeltaBytes: entries.reduce((sum, entry) => sum + entry.positiveRssDeltaBytes, 0),\n },\n thresholds,\n modules: sortedModules,\n candidates: sortedModules.filter((module) => module.candidateReasons.length > 0),\n buckets,\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["state"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
function parseNumberWithDefault(raw, fallback, options) {
|
|
2
|
+
if (raw == null) return fallback;
|
|
3
|
+
const trimmed = raw.trim();
|
|
4
|
+
if (!trimmed) return fallback;
|
|
5
|
+
const value = options?.integer ? Number.parseInt(trimmed, 10) : Number(trimmed);
|
|
6
|
+
if (!Number.isFinite(value)) return fallback;
|
|
7
|
+
const min = options?.min ?? -Infinity;
|
|
8
|
+
if (value < min) return fallback;
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
parseNumberWithDefault
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=number.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/lib/number.ts"],
|
|
4
|
+
"sourcesContent": ["export function parseNumberWithDefault(\n raw: string | null | undefined,\n fallback: number,\n options?: { min?: number; integer?: boolean },\n): number {\n if (raw == null) return fallback\n const trimmed = raw.trim()\n if (!trimmed) return fallback\n const value = options?.integer ? Number.parseInt(trimmed, 10) : Number(trimmed)\n if (!Number.isFinite(value)) return fallback\n const min = options?.min ?? -Infinity\n if (value < min) return fallback\n return value\n}\n"],
|
|
5
|
+
"mappings": "AAAO,SAAS,uBACd,KACA,UACA,SACQ;AACR,MAAI,OAAO,KAAM,QAAO;AACxB,QAAM,UAAU,IAAI,KAAK;AACzB,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,QAAQ,SAAS,UAAU,OAAO,SAAS,SAAS,EAAE,IAAI,OAAO,OAAO;AAC9E,MAAI,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AACpC,QAAM,MAAM,SAAS,OAAO;AAC5B,MAAI,QAAQ,IAAK,QAAO;AACxB,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import { parseBooleanWithDefault } from "@open-mercato/shared/lib/boolean";
|
|
2
|
+
import { parseNumberWithDefault } from "@open-mercato/shared/lib/number";
|
|
2
3
|
const DEFAULT_SEARCH_MIN_TOKEN_LENGTH = 3;
|
|
3
4
|
const DEFAULT_BLOCKLIST = ["password", "token", "secret", "hash"];
|
|
4
5
|
function parseBoolean(raw, fallback) {
|
|
5
6
|
return parseBooleanWithDefault(raw, fallback);
|
|
6
7
|
}
|
|
7
8
|
function parseNumber(raw, fallback, min = 1) {
|
|
8
|
-
|
|
9
|
-
const value = Number.parseInt(raw, 10);
|
|
10
|
-
if (!Number.isFinite(value)) return fallback;
|
|
11
|
-
if (value < min) return fallback;
|
|
12
|
-
return value;
|
|
9
|
+
return parseNumberWithDefault(raw, fallback, { integer: true, min });
|
|
13
10
|
}
|
|
14
11
|
function parseHashAlgorithm(raw) {
|
|
15
12
|
const value = (raw ?? "").trim().toLowerCase();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/search/config.ts"],
|
|
4
|
-
"sourcesContent": ["import { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'\n\nexport type SearchConfig = {\n enabled: boolean\n minTokenLength: number\n enablePartials: boolean\n hashAlgorithm: 'sha256' | 'sha1' | 'md5'\n storeRawTokens: boolean\n blocklistedFields: string[]\n}\n\nexport const DEFAULT_SEARCH_MIN_TOKEN_LENGTH = 3\n\nconst DEFAULT_BLOCKLIST = ['password', 'token', 'secret', 'hash']\n\nfunction parseBoolean(raw: string | undefined, fallback: boolean): boolean {\n return parseBooleanWithDefault(raw, fallback)\n}\n\nfunction parseNumber(raw: string | undefined, fallback: number, min = 1): number {\n
|
|
5
|
-
"mappings": "AAAA,SAAS,+BAA+B;
|
|
4
|
+
"sourcesContent": ["import { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'\nimport { parseNumberWithDefault } from '@open-mercato/shared/lib/number'\n\nexport type SearchConfig = {\n enabled: boolean\n minTokenLength: number\n enablePartials: boolean\n hashAlgorithm: 'sha256' | 'sha1' | 'md5'\n storeRawTokens: boolean\n blocklistedFields: string[]\n}\n\nexport const DEFAULT_SEARCH_MIN_TOKEN_LENGTH = 3\n\nconst DEFAULT_BLOCKLIST = ['password', 'token', 'secret', 'hash']\n\nfunction parseBoolean(raw: string | undefined, fallback: boolean): boolean {\n return parseBooleanWithDefault(raw, fallback)\n}\n\nfunction parseNumber(raw: string | undefined, fallback: number, min = 1): number {\n return parseNumberWithDefault(raw, fallback, { integer: true, min })\n}\n\nfunction parseHashAlgorithm(raw: string | undefined): 'sha256' | 'sha1' | 'md5' {\n const value = (raw ?? '').trim().toLowerCase()\n if (value === 'sha1') return 'sha1'\n if (value === 'md5') return 'md5'\n return 'sha256'\n}\n\nexport function resolveSearchConfig(): SearchConfig {\n return {\n enabled: parseBoolean(process.env.OM_SEARCH_ENABLED, true),\n minTokenLength: resolveSearchMinTokenLength(),\n enablePartials: parseBoolean(process.env.OM_SEARCH_ENABLE_PARTIAL, true),\n hashAlgorithm: parseHashAlgorithm(process.env.OM_SEARCH_HASH_ALGO),\n storeRawTokens: parseBoolean(process.env.OM_SEARCH_STORE_RAW_TOKENS, false),\n blocklistedFields: (process.env.OM_SEARCH_FIELD_BLOCKLIST ?? '')\n .split(',')\n .map((entry) => entry.trim())\n .filter((entry) => entry.length > 0)\n .filter((value, index, arr) => arr.indexOf(value) === index)\n .map((entry) => entry.toLowerCase())\n .concat(DEFAULT_BLOCKLIST)\n .filter((value, index, arr) => arr.indexOf(value) === index),\n }\n}\n\n/**\n * Browser-safe accessor for the minimum search token length.\n *\n * Why: client components (e.g. global search dialog) must mirror the server-side\n * tokenizer's `minTokenLength` so the UI gates the request before hitting an\n * empty result set. Pulling the value through this single helper keeps the env\n * contract (`OM_SEARCH_MIN_LEN`) authoritative on both sides.\n *\n * How to apply: call from anywhere \u2014 server, client (when the host app exposes\n * `OM_SEARCH_MIN_LEN` through `next.config.ts`'s `env` block), or tests.\n */\nexport function resolveSearchMinTokenLength(): number {\n return parseNumber(process.env.OM_SEARCH_MIN_LEN, DEFAULT_SEARCH_MIN_TOKEN_LENGTH, 1)\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,+BAA+B;AACxC,SAAS,8BAA8B;AAWhC,MAAM,kCAAkC;AAE/C,MAAM,oBAAoB,CAAC,YAAY,SAAS,UAAU,MAAM;AAEhE,SAAS,aAAa,KAAyB,UAA4B;AACzE,SAAO,wBAAwB,KAAK,QAAQ;AAC9C;AAEA,SAAS,YAAY,KAAyB,UAAkB,MAAM,GAAW;AAC/E,SAAO,uBAAuB,KAAK,UAAU,EAAE,SAAS,MAAM,IAAI,CAAC;AACrE;AAEA,SAAS,mBAAmB,KAAoD;AAC9E,QAAM,SAAS,OAAO,IAAI,KAAK,EAAE,YAAY;AAC7C,MAAI,UAAU,OAAQ,QAAO;AAC7B,MAAI,UAAU,MAAO,QAAO;AAC5B,SAAO;AACT;AAEO,SAAS,sBAAoC;AAClD,SAAO;AAAA,IACL,SAAS,aAAa,QAAQ,IAAI,mBAAmB,IAAI;AAAA,IACzD,gBAAgB,4BAA4B;AAAA,IAC5C,gBAAgB,aAAa,QAAQ,IAAI,0BAA0B,IAAI;AAAA,IACvE,eAAe,mBAAmB,QAAQ,IAAI,mBAAmB;AAAA,IACjE,gBAAgB,aAAa,QAAQ,IAAI,4BAA4B,KAAK;AAAA,IAC1E,oBAAoB,QAAQ,IAAI,6BAA6B,IAC1D,MAAM,GAAG,EACT,IAAI,CAAC,UAAU,MAAM,KAAK,CAAC,EAC3B,OAAO,CAAC,UAAU,MAAM,SAAS,CAAC,EAClC,OAAO,CAAC,OAAO,OAAO,QAAQ,IAAI,QAAQ,KAAK,MAAM,KAAK,EAC1D,IAAI,CAAC,UAAU,MAAM,YAAY,CAAC,EAClC,OAAO,iBAAiB,EACxB,OAAO,CAAC,OAAO,OAAO,QAAQ,IAAI,QAAQ,KAAK,MAAM,KAAK;AAAA,EAC/D;AACF;AAaO,SAAS,8BAAsC;AACpD,SAAO,YAAY,QAAQ,IAAI,mBAAmB,iCAAiC,CAAC;AACtF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/lib/version.js
CHANGED
package/dist/lib/version.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/version.ts"],
|
|
4
|
-
"sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.6.6-develop.
|
|
4
|
+
"sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.6.6-develop.6385.1.9a81faa5f0'\nexport const appVersion = APP_VERSION\n"],
|
|
5
5
|
"mappings": "AACO,MAAM,cAAc;AACpB,MAAM,aAAa;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/modules/registry.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ReactNode } from 'react'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi/types'\nimport type { SyncCrudEventResult } from '../lib/crud/sync-event-types'\nimport type { DashboardWidgetModule } from './dashboard/widgets'\nimport type { InjectionAnyWidgetModule, ModuleInjectionTable } from './widgets/injection'\nimport type { IntegrationBundle, IntegrationDefinition } from './integrations/types'\nimport {\n applyApiOverridesToManifests,\n applyModuleOverridesToModules,\n applyPageOverridesToManifests,\n composeApiRouteOverrides,\n composePageRouteOverrides,\n} from './overrides'\n\n// Context passed to dynamic metadata guards\nexport type RouteVisibilityContext = { path?: string; auth?: any }\n\n/**\n * Portal sidebar navigation hint. When declared on a portal page's metadata,\n * the page is auto-listed in the portal sidebar (subject to RBAC) by the\n * `/api/customer_accounts/portal/nav` endpoint.\n *\n * Absence of `nav` means the page is routable but not auto-listed (useful for\n * detail pages, create forms, etc.).\n */\nexport type PortalNavMetadata = {\n label: string\n labelKey?: string\n group?: 'main' | 'account'\n order?: number\n icon?: string\n}\n\n// Metadata you can export from page.meta.ts or directly from a server page\nexport type PageMetadata = {\n requireAuth?: boolean\n /** @deprecated Use `requireFeatures` instead \u2014 role names are mutable and can be spoofed */\n requireRoles?: readonly string[]\n // Optional fine-grained feature requirements\n requireFeatures?: readonly string[]\n // Portal: require customer (portal user) authentication instead of staff auth\n requireCustomerAuth?: boolean\n // Portal: require customer-specific features (checked against CustomerRbacService)\n requireCustomerFeatures?: readonly string[]\n // Portal: optional sidebar presentation hint (auto-listed by portal nav endpoint)\n nav?: PortalNavMetadata\n // Titles and grouping (aliases supported)\n title?: string\n titleKey?: string\n pageTitle?: string\n pageTitleKey?: string\n group?: string\n groupKey?: string\n pageGroup?: string\n pageGroupKey?: string\n // Ordering and visuals\n order?: number\n pageOrder?: number\n priority?: number\n pagePriority?: number\n icon?: ReactNode\n navHidden?: boolean\n // Dynamic flags\n visible?: (ctx: RouteVisibilityContext) => boolean | Promise<boolean>\n enabled?: (ctx: RouteVisibilityContext) => boolean | Promise<boolean>\n // Optional static breadcrumb trail for header\n breadcrumb?: Array<{ label: string; labelKey?: string; href?: string }>\n // Navigation context for tiered navigation:\n // - 'main' (default): Main sidebar business operations\n // - 'admin': Collapsible \"Settings & Admin\" section at bottom of sidebar\n // - 'settings': Hidden from sidebar, only accessible via Settings hub page\n // - 'profile': Profile dropdown items\n pageContext?: 'main' | 'admin' | 'settings' | 'profile'\n placement?: {\n section: string\n sectionLabel?: string\n sectionLabelKey?: string\n order?: number\n }\n}\n\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'\n\nexport type ApiHandler = (req: Request, ctx?: any) => Promise<Response> | Response\n\nexport type ModuleSubscriberHandler = (\n payload: any,\n ctx: any\n) => Promise<void | SyncCrudEventResult> | void | SyncCrudEventResult\n\nexport type ModuleWorkerHandler = (job: unknown, ctx: unknown) => Promise<void> | void\n\nexport type ModuleRoute = {\n pattern?: string\n path?: string\n requireAuth?: boolean\n /** @deprecated Use `requireFeatures` instead \u2014 role names are mutable and can be spoofed */\n requireRoles?: string[]\n // Optional fine-grained feature requirements\n requireFeatures?: string[]\n // Portal: require customer (portal user) authentication instead of staff auth\n requireCustomerAuth?: boolean\n // Portal: require customer-specific features (checked against CustomerRbacService)\n requireCustomerFeatures?: string[]\n // Portal: optional sidebar presentation hint (auto-listed by portal nav endpoint)\n nav?: PortalNavMetadata\n title?: string\n titleKey?: string\n group?: string\n groupKey?: string\n icon?: ReactNode\n order?: number\n priority?: number\n navHidden?: boolean\n visible?: (ctx: RouteVisibilityContext) => boolean | Promise<boolean>\n enabled?: (ctx: RouteVisibilityContext) => boolean | Promise<boolean>\n breadcrumb?: Array<{ label: string; labelKey?: string; href?: string }>\n pageContext?: 'main' | 'admin' | 'settings' | 'profile'\n placement?: {\n section: string\n sectionLabel?: string\n sectionLabelKey?: string\n order?: number\n }\n Component: (props: any) => ReactNode | Promise<ReactNode>\n}\n\nexport type ModuleRouteMetadata = Omit<ModuleRoute, 'Component'>\n\nexport type ModuleApiLegacy = {\n method: HttpMethod\n path: string\n handler: ApiHandler\n metadata?: Record<string, unknown>\n docs?: OpenApiMethodDoc\n}\n\nexport type ModuleApiRouteFile = {\n path: string\n handlers: Partial<Record<HttpMethod, ApiHandler>>\n requireAuth?: boolean\n /** @deprecated Use `requireFeatures` instead \u2014 role names are mutable and can be spoofed */\n requireRoles?: string[]\n // Optional fine-grained feature requirements for the entire route file\n // Note: per-method feature requirements should be expressed inside metadata\n requireFeatures?: string[]\n docs?: OpenApiRouteDoc\n metadata?: Partial<Record<HttpMethod, unknown>>\n}\n\nexport type ModuleApi = ModuleApiLegacy | ModuleApiRouteFile\n\nexport type RouteMatchParams = Record<string, string | string[]>\n\nexport type FrontendRouteManifestEntry = Omit<ModuleRoute, 'Component'> & {\n moduleId: string\n load: () => Promise<ModuleRoute['Component']>\n}\n\nexport type BackendRouteManifestEntry = Omit<ModuleRoute, 'Component'> & {\n moduleId: string\n load: () => Promise<ModuleRoute['Component']>\n}\n\nexport type ApiRouteManifestEntry = {\n moduleId: string\n kind: 'route-file' | 'legacy'\n path: string\n methods: HttpMethod[]\n method?: HttpMethod\n load: () => Promise<Record<string, unknown>>\n}\n\nexport type ModuleCli = {\n command: string\n run: (argv: string[]) => Promise<void> | void\n}\n\nexport type ModuleSubscriber = {\n id: string\n event: string\n persistent?: boolean\n sync?: boolean\n priority?: number\n handler: ModuleSubscriberHandler\n}\n\nexport type ModuleWorker = {\n id: string\n queue: string\n concurrency: number\n handler: ModuleWorkerHandler\n}\n\nexport type ModuleInfo = {\n name?: string\n title?: string\n version?: string\n description?: string\n author?: string\n license?: string\n homepage?: string\n copyright?: string\n // Optional hard dependencies: module ids that must be enabled\n requires?: string[]\n // Whether this module can be ejected into the app's src/modules/ for customization\n ejectable?: boolean\n}\n\nexport type ModuleDashboardWidgetEntry = {\n moduleId: string\n key: string\n source: 'app' | 'package'\n loader: () => Promise<DashboardWidgetModule<any>>\n}\n\nexport type ModuleInjectionWidgetEntry = {\n moduleId: string\n key: string\n source: 'app' | 'package'\n widgetId?: string\n loader: () => Promise<InjectionAnyWidgetModule<any, any>>\n}\n\nexport type Module = {\n id: string\n info?: ModuleInfo\n backendRoutes?: ModuleRoute[]\n frontendRoutes?: ModuleRoute[]\n apis?: ModuleApi[]\n cli?: ModuleCli[]\n translations?: Record<string, Record<string, string>>\n // Optional: per-module feature declarations discovered from acl.ts (module root)\n features?: Array<{ id: string; title: string; module: string }>\n // Auto-discovered event subscribers\n subscribers?: ModuleSubscriber[]\n // Auto-discovered queue workers\n workers?: ModuleWorker[]\n // Optional: per-module declared entity extensions and custom fields (static)\n // Extensions discovered from data/extensions.ts; Custom fields discovered from ce.ts (entities[].fields)\n entityExtensions?: import('./entities').EntityExtension[]\n customFieldSets?: import('./entities').CustomFieldSet[]\n // Optional: per-module declared custom entities (virtual/logical entities)\n // Discovered from ce.ts (module root). Each entry represents an entityId with optional label/description.\n customEntities?: Array<{ id: string; label?: string; description?: string }>\n dashboardWidgets?: ModuleDashboardWidgetEntry[]\n injectionWidgets?: ModuleInjectionWidgetEntry[]\n injectionTable?: ModuleInjectionTable\n // Optional: per-module vector search configuration (discovered from vector.ts)\n vector?: import('./vector').VectorModuleConfig\n // Optional: module-specific tenant setup configuration (from setup.ts)\n setup?: import('./setup').ModuleSetupConfig\n // Optional: default encryption maps owned by the module (from encryption.ts)\n defaultEncryptionMaps?: import('./encryption').ModuleEncryptionMap[]\n // Optional: integration marketplace declarations discovered from integration.ts\n integrations?: IntegrationDefinition[]\n bundles?: IntegrationBundle[]\n}\n\nfunction normPath(s: string) {\n return (s.startsWith('/') ? s : '/' + s).replace(/\\/+$/, '') || '/'\n}\n\n// 0 = literal (most specific), 1 = dynamic [param], 2 = catch-all [...param] or [[...param]]\nfunction segmentSpecificity(seg: string): 0 | 1 | 2 {\n if (seg.startsWith('[[...') || seg.startsWith('[...')) return 2\n if (seg.startsWith('[')) return 1\n return 0\n}\n\nfunction compareRouteSpecificity(aPattern: string, bPattern: string): number {\n const aSegs = aPattern.split('/')\n const bSegs = bPattern.split('/')\n const len = Math.max(aSegs.length, bSegs.length)\n for (let i = 0; i < len; i++) {\n const av = i < aSegs.length ? segmentSpecificity(aSegs[i]) : -1\n const bv = i < bSegs.length ? segmentSpecificity(bSegs[i]) : -1\n if (av !== bv) return av - bv\n }\n return 0\n}\n\nexport function sortRoutesBySpecificity<T extends { pattern?: string; path?: string }>(routes: T[]): T[] {\n return [...routes].sort((a, b) =>\n compareRouteSpecificity(a.pattern ?? a.path ?? '/', b.pattern ?? b.path ?? '/'),\n )\n}\n\n// Memoized per-array sorted view, so direct callers (e.g., the Next.js catch-all\n// routes that import generated `frontendRoutes`/`backendRoutes`/`apiRoutes`\n// arrays) match against a specificity-sorted view even if they never call\n// `register*RouteManifests`. Keyed by array reference; generated arrays are\n// module-level constants so this caches once per process.\nconst sortedRoutesCache = new WeakMap<object, readonly unknown[]>()\n\nfunction ensureSortedRoutes<T extends { pattern?: string; path?: string }>(routes: readonly T[]): readonly T[] {\n const cached = sortedRoutesCache.get(routes) as readonly T[] | undefined\n if (cached) return cached\n const sorted = sortRoutesBySpecificity([...routes])\n sortedRoutesCache.set(routes, sorted)\n return sorted\n}\n\nexport function matchRoutePattern(pattern: string, pathname: string): RouteMatchParams | undefined {\n const p = normPath(pattern)\n const u = normPath(pathname)\n const pSegs = p.split('/').slice(1)\n const uSegs = u.split('/').slice(1)\n const params: Record<string, string | string[]> = {}\n let i = 0\n for (let j = 0; j < pSegs.length; j++, i++) {\n const seg = pSegs[j]\n const mCatchAll = seg.match(/^\\[\\.\\.\\.(.+)\\]$/)\n const mOptCatch = seg.match(/^\\[\\[\\.\\.\\.(.+)\\]\\]$/)\n const mDyn = seg.match(/^\\[(.+)\\]$/)\n if (mCatchAll) {\n const key = mCatchAll[1]\n if (i >= uSegs.length) return undefined\n params[key] = uSegs.slice(i)\n return params\n } else if (mOptCatch) {\n const key = mOptCatch[1]\n params[key] = i < uSegs.length ? uSegs.slice(i) : []\n return params\n } else if (mDyn) {\n if (i >= uSegs.length) return undefined\n params[mDyn[1]] = uSegs[i]\n } else {\n if (i >= uSegs.length || uSegs[i].toLowerCase() !== seg.toLowerCase()) return undefined\n }\n }\n if (i !== uSegs.length) return undefined\n return params\n}\n\nfunction getPattern(r: ModuleRoute) {\n return r.pattern ?? r.path ?? '/'\n}\n\nexport function findFrontendMatch(modules: Module[], pathname: string): { route: ModuleRoute; params: Record<string, string | string[]> } | undefined {\n for (const m of modules) {\n const routes = m.frontendRoutes ?? []\n for (const r of routes) {\n const params = matchRoutePattern(getPattern(r), pathname)\n if (params) return { route: r, params }\n }\n }\n}\n\nexport function findBackendMatch(modules: Module[], pathname: string): { route: ModuleRoute; params: Record<string, string | string[]> } | undefined {\n for (const m of modules) {\n const routes = m.backendRoutes ?? []\n for (const r of routes) {\n const params = matchRoutePattern(getPattern(r), pathname)\n if (params) return { route: r, params }\n }\n }\n}\n\nexport function findApi(modules: Module[], method: HttpMethod, pathname: string): { handler: ApiHandler; params: Record<string, string | string[]>; requireAuth?: boolean; requireRoles?: string[]; metadata?: any } | undefined {\n for (const m of modules) {\n const apis = m.apis ?? []\n for (const a of apis) {\n if ('handlers' in a) {\n const params = matchRoutePattern(a.path, pathname)\n const handler = (a.handlers as any)[method]\n if (params && handler) return { handler, params, requireAuth: a.requireAuth, requireRoles: (a as any).requireRoles, metadata: (a as any).metadata }\n } else {\n const al = a as ModuleApiLegacy\n if (al.method !== method) continue\n const params = matchRoutePattern(al.path, pathname)\n if (params) {\n return { handler: al.handler, params, metadata: al.metadata }\n }\n }\n }\n }\n}\n\nexport function findRouteManifestMatch<T extends { pattern?: string; path?: string }>(\n routes: T[],\n pathname: string\n): { route: T; params: RouteMatchParams } | undefined {\n for (const route of ensureSortedRoutes(routes)) {\n const params = matchRoutePattern(route.pattern ?? route.path ?? '/', pathname)\n if (params) {\n return { route, params }\n }\n }\n}\n\nexport function findApiRouteManifestMatch<T extends { path: string; methods: HttpMethod[] }>(\n routes: T[],\n method: HttpMethod,\n pathname: string\n): { route: T; params: RouteMatchParams } | undefined {\n for (const route of ensureSortedRoutes(routes)) {\n if (!route.methods.includes(method)) continue\n const params = matchRoutePattern(route.path, pathname)\n if (params) {\n return { route, params }\n }\n }\n}\n\nexport function resolvePageRouteMetadata(pattern: string, metadata: PageMetadata | null | undefined): ModuleRouteMetadata {\n return {\n pattern: pattern || '/',\n requireAuth: metadata?.requireAuth,\n requireRoles: metadata?.requireRoles ? [...metadata.requireRoles] : undefined,\n requireFeatures: metadata?.requireFeatures ? [...metadata.requireFeatures] : undefined,\n requireCustomerAuth: metadata?.requireCustomerAuth,\n requireCustomerFeatures: metadata?.requireCustomerFeatures ? [...metadata.requireCustomerFeatures] : undefined,\n nav: metadata?.nav,\n title: metadata?.pageTitle ?? metadata?.title,\n titleKey: metadata?.pageTitleKey ?? metadata?.titleKey,\n group: metadata?.pageGroup ?? metadata?.group,\n groupKey: metadata?.pageGroupKey ?? metadata?.groupKey,\n icon: metadata?.icon,\n order: metadata?.pageOrder ?? metadata?.order,\n priority: metadata?.pagePriority ?? metadata?.priority,\n navHidden: metadata?.navHidden,\n visible: metadata?.visible,\n enabled: metadata?.enabled,\n breadcrumb: metadata?.breadcrumb,\n pageContext: metadata?.pageContext,\n placement: metadata?.placement,\n }\n}\n\nlet _backendRouteManifests: BackendRouteManifestEntry[] | null = null\n\nexport function registerBackendRouteManifests(routes: BackendRouteManifestEntry[]) {\n const pageOverrides = composePageRouteOverrides()\n const finalRoutes = Object.keys(pageOverrides).length === 0\n ? routes\n : applyPageOverridesToManifests(routes, pageOverrides, 'backend')\n _backendRouteManifests = sortRoutesBySpecificity(finalRoutes)\n}\n\nexport function getBackendRouteManifests(): BackendRouteManifestEntry[] {\n return _backendRouteManifests ?? []\n}\n\nlet _frontendRouteManifests: FrontendRouteManifestEntry[] | null = null\n\nexport function registerFrontendRouteManifests(routes: FrontendRouteManifestEntry[]) {\n const pageOverrides = composePageRouteOverrides()\n const finalRoutes = Object.keys(pageOverrides).length === 0\n ? routes\n : applyPageOverridesToManifests(routes, pageOverrides, 'frontend')\n _frontendRouteManifests = sortRoutesBySpecificity(finalRoutes)\n}\n\nexport function getFrontendRouteManifests(): FrontendRouteManifestEntry[] {\n return _frontendRouteManifests ?? []\n}\n\nlet _apiRouteManifests: ApiRouteManifestEntry[] | null = null\n\nexport function registerApiRouteManifests(routes: ApiRouteManifestEntry[]) {\n // Apply any `entry.overrides.routes.api` overrides registered through the\n // unified `modules.ts` dispatcher or programmatic API before storing the\n // manifest. The composer is cheap and returns an empty object when no\n // overrides exist, so this is a no-op for apps that do not opt in.\n const routeOverrides = composeApiRouteOverrides()\n const finalRoutes = Object.keys(routeOverrides).length === 0\n ? routes\n : applyApiOverridesToManifests(routes, routeOverrides)\n _apiRouteManifests = sortRoutesBySpecificity(finalRoutes)\n}\n\nexport function getApiRouteManifests(): ApiRouteManifestEntry[] {\n return _apiRouteManifests ?? []\n}\n\n// CLI modules registry - shared between CLI and module workers\nlet _cliModules: Module[] | null = null\n\nexport function registerCliModules(modules: Module[]) {\n if (_cliModules !== null && process.env.NODE_ENV === 'development') {\n console.debug('[Bootstrap] CLI modules re-registered (this may occur during HMR)')\n }\n _cliModules = applyModuleOverridesToModules(modules)\n}\n\nexport function getCliModules(): Module[] {\n // Return empty array if not registered - allows generate command to work without bootstrap\n return _cliModules ?? []\n}\n\nexport function hasCliModules(): boolean {\n return _cliModules !== null && _cliModules.length > 0\n}\n\nexport function getDefaultEncryptionMaps(modules: Module[]): import('./encryption').ModuleEncryptionMap[] {\n const byEntityId = new Map<string, { moduleId: string; map: import('./encryption').ModuleEncryptionMap }>()\n\n for (const mod of modules) {\n for (const entry of mod.defaultEncryptionMaps ?? []) {\n const previous = byEntityId.get(entry.entityId)\n if (previous) {\n throw new Error(\n `[registry] Duplicate default encryption map for \"${entry.entityId}\" declared by \"${previous.moduleId}\" and \"${mod.id}\"`\n )\n }\n byEntityId.set(entry.entityId, {\n moduleId: mod.id,\n map: {\n entityId: entry.entityId,\n fields: entry.fields.map((field) => ({\n field: field.field,\n hashField: field.hashField ?? null,\n })),\n },\n })\n }\n }\n\n return Array.from(byEntityId.values(), ({ map }) => map)\n}\n\nfunction ensureLazyHandler<T extends (...args: any[]) => any>(\n loaded: unknown,\n kind: 'subscriber' | 'worker',\n id: string\n): T {\n const handler = typeof loaded === 'function'\n ? loaded\n : loaded && typeof loaded === 'object' && 'default' in loaded\n ? (loaded as Record<string, unknown>).default\n : null\n if (typeof handler !== 'function') {\n throw new Error(`[registry] Invalid ${kind} module \"${id}\" (missing default export handler)`)\n }\n return handler as T\n}\n\nexport function createLazyModuleSubscriber(\n loadModule: () => Promise<unknown>,\n id: string\n): ModuleSubscriberHandler {\n let handlerPromise: Promise<ModuleSubscriberHandler> | null = null\n return async (payload, ctx) => {\n handlerPromise ??= loadModule().then((loaded) =>\n ensureLazyHandler<ModuleSubscriberHandler>(loaded, 'subscriber', id)\n )\n const handler = await handlerPromise\n return handler(payload, ctx)\n }\n}\n\nexport function createLazyModuleWorker(\n loadModule: () => Promise<unknown>,\n id: string\n): ModuleWorkerHandler {\n let handlerPromise: Promise<ModuleWorkerHandler> | null = null\n return async (job, ctx) => {\n handlerPromise ??= loadModule().then((loaded) =>\n ensureLazyHandler<ModuleWorkerHandler>(loaded, 'worker', id)\n )\n const handler = await handlerPromise\n return handler(job, ctx)\n }\n}\n"],
|
|
5
|
-
"mappings": "AAMA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;
|
|
4
|
+
"sourcesContent": ["import type { ReactNode } from 'react'\nimport type { OpenApiRouteDoc, OpenApiMethodDoc } from '@open-mercato/shared/lib/openapi/types'\nimport type { SyncCrudEventResult } from '../lib/crud/sync-event-types'\nimport type { DashboardWidgetModule } from './dashboard/widgets'\nimport type { InjectionAnyWidgetModule, ModuleInjectionTable } from './widgets/injection'\nimport type { IntegrationBundle, IntegrationDefinition } from './integrations/types'\nimport {\n applyApiOverridesToManifests,\n applyModuleOverridesToModules,\n applyPageOverridesToManifests,\n composeApiRouteOverrides,\n composePageRouteOverrides,\n} from './overrides'\n\n// Context passed to dynamic metadata guards\nexport type RouteVisibilityContext = { path?: string; auth?: any }\n\n/**\n * Portal sidebar navigation hint. When declared on a portal page's metadata,\n * the page is auto-listed in the portal sidebar (subject to RBAC) by the\n * `/api/customer_accounts/portal/nav` endpoint.\n *\n * Absence of `nav` means the page is routable but not auto-listed (useful for\n * detail pages, create forms, etc.).\n */\nexport type PortalNavMetadata = {\n label: string\n labelKey?: string\n group?: 'main' | 'account'\n order?: number\n icon?: string\n}\n\n// Metadata you can export from page.meta.ts or directly from a server page\nexport type PageMetadata = {\n requireAuth?: boolean\n /** @deprecated Use `requireFeatures` instead \u2014 role names are mutable and can be spoofed */\n requireRoles?: readonly string[]\n // Optional fine-grained feature requirements\n requireFeatures?: readonly string[]\n // Portal: require customer (portal user) authentication instead of staff auth\n requireCustomerAuth?: boolean\n // Portal: require customer-specific features (checked against CustomerRbacService)\n requireCustomerFeatures?: readonly string[]\n // Portal: optional sidebar presentation hint (auto-listed by portal nav endpoint)\n nav?: PortalNavMetadata\n // Titles and grouping (aliases supported)\n title?: string\n titleKey?: string\n pageTitle?: string\n pageTitleKey?: string\n group?: string\n groupKey?: string\n pageGroup?: string\n pageGroupKey?: string\n // Ordering and visuals\n order?: number\n pageOrder?: number\n priority?: number\n pagePriority?: number\n icon?: ReactNode\n navHidden?: boolean\n // Dynamic flags\n visible?: (ctx: RouteVisibilityContext) => boolean | Promise<boolean>\n enabled?: (ctx: RouteVisibilityContext) => boolean | Promise<boolean>\n // Optional static breadcrumb trail for header\n breadcrumb?: Array<{ label: string; labelKey?: string; href?: string }>\n // Navigation context for tiered navigation:\n // - 'main' (default): Main sidebar business operations\n // - 'admin': Collapsible \"Settings & Admin\" section at bottom of sidebar\n // - 'settings': Hidden from sidebar, only accessible via Settings hub page\n // - 'profile': Profile dropdown items\n pageContext?: 'main' | 'admin' | 'settings' | 'profile'\n placement?: {\n section: string\n sectionLabel?: string\n sectionLabelKey?: string\n order?: number\n }\n}\n\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'\n\nexport type ApiHandler = (req: Request, ctx?: any) => Promise<Response> | Response\n\nexport type ModuleSubscriberHandler = (\n payload: any,\n ctx: any\n) => Promise<void | SyncCrudEventResult> | void | SyncCrudEventResult\n\nexport type ModuleWorkerHandler = (job: unknown, ctx: unknown) => Promise<void> | void\n\nexport type ModuleRoute = {\n pattern?: string\n path?: string\n requireAuth?: boolean\n /** @deprecated Use `requireFeatures` instead \u2014 role names are mutable and can be spoofed */\n requireRoles?: string[]\n // Optional fine-grained feature requirements\n requireFeatures?: string[]\n // Portal: require customer (portal user) authentication instead of staff auth\n requireCustomerAuth?: boolean\n // Portal: require customer-specific features (checked against CustomerRbacService)\n requireCustomerFeatures?: string[]\n // Portal: optional sidebar presentation hint (auto-listed by portal nav endpoint)\n nav?: PortalNavMetadata\n title?: string\n titleKey?: string\n group?: string\n groupKey?: string\n icon?: ReactNode\n order?: number\n priority?: number\n navHidden?: boolean\n visible?: (ctx: RouteVisibilityContext) => boolean | Promise<boolean>\n enabled?: (ctx: RouteVisibilityContext) => boolean | Promise<boolean>\n breadcrumb?: Array<{ label: string; labelKey?: string; href?: string }>\n pageContext?: 'main' | 'admin' | 'settings' | 'profile'\n placement?: {\n section: string\n sectionLabel?: string\n sectionLabelKey?: string\n order?: number\n }\n Component: (props: any) => ReactNode | Promise<ReactNode>\n}\n\nexport type ModuleRouteMetadata = Omit<ModuleRoute, 'Component'>\n\nexport type ModuleApiLegacy = {\n method: HttpMethod\n path: string\n handler: ApiHandler\n metadata?: Record<string, unknown>\n docs?: OpenApiMethodDoc\n}\n\nexport type ModuleApiRouteFile = {\n path: string\n handlers: Partial<Record<HttpMethod, ApiHandler>>\n requireAuth?: boolean\n /** @deprecated Use `requireFeatures` instead \u2014 role names are mutable and can be spoofed */\n requireRoles?: string[]\n // Optional fine-grained feature requirements for the entire route file\n // Note: per-method feature requirements should be expressed inside metadata\n requireFeatures?: string[]\n docs?: OpenApiRouteDoc\n metadata?: Partial<Record<HttpMethod, unknown>>\n}\n\nexport type ModuleApi = ModuleApiLegacy | ModuleApiRouteFile\n\nexport type RouteMatchParams = Record<string, string | string[]>\n\nexport type FrontendRouteManifestEntry = Omit<ModuleRoute, 'Component'> & {\n moduleId: string\n load: () => Promise<ModuleRoute['Component']>\n}\n\nexport type BackendRouteManifestEntry = Omit<ModuleRoute, 'Component'> & {\n moduleId: string\n load: () => Promise<ModuleRoute['Component']>\n}\n\nexport type ApiRouteManifestEntry = {\n moduleId: string\n kind: 'route-file' | 'legacy'\n path: string\n methods: HttpMethod[]\n method?: HttpMethod\n load: () => Promise<Record<string, unknown>>\n}\n\nexport type ModuleCli = {\n command: string\n run: (argv: string[]) => Promise<void> | void\n}\n\nexport type ModuleSubscriber = {\n id: string\n moduleId?: string\n event: string\n persistent?: boolean\n sync?: boolean\n priority?: number\n handler: ModuleSubscriberHandler\n}\n\nexport type ModuleWorker = {\n id: string\n moduleId?: string\n queue: string\n concurrency: number\n handler: ModuleWorkerHandler\n}\n\nexport type ModuleInfo = {\n name?: string\n title?: string\n version?: string\n description?: string\n author?: string\n license?: string\n homepage?: string\n copyright?: string\n // Optional hard dependencies: module ids that must be enabled\n requires?: string[]\n // Whether this module can be ejected into the app's src/modules/ for customization\n ejectable?: boolean\n}\n\nexport type ModuleDashboardWidgetEntry = {\n moduleId: string\n key: string\n source: 'app' | 'package'\n loader: () => Promise<DashboardWidgetModule<any>>\n}\n\nexport type ModuleInjectionWidgetEntry = {\n moduleId: string\n key: string\n source: 'app' | 'package'\n widgetId?: string\n loader: () => Promise<InjectionAnyWidgetModule<any, any>>\n}\n\nexport type Module = {\n id: string\n info?: ModuleInfo\n backendRoutes?: ModuleRoute[]\n frontendRoutes?: ModuleRoute[]\n apis?: ModuleApi[]\n cli?: ModuleCli[]\n translations?: Record<string, Record<string, string>>\n // Optional: per-module feature declarations discovered from acl.ts (module root)\n features?: Array<{ id: string; title: string; module: string }>\n // Auto-discovered event subscribers\n subscribers?: ModuleSubscriber[]\n // Auto-discovered queue workers\n workers?: ModuleWorker[]\n // Optional: per-module declared entity extensions and custom fields (static)\n // Extensions discovered from data/extensions.ts; Custom fields discovered from ce.ts (entities[].fields)\n entityExtensions?: import('./entities').EntityExtension[]\n customFieldSets?: import('./entities').CustomFieldSet[]\n // Optional: per-module declared custom entities (virtual/logical entities)\n // Discovered from ce.ts (module root). Each entry represents an entityId with optional label/description.\n customEntities?: Array<{ id: string; label?: string; description?: string }>\n dashboardWidgets?: ModuleDashboardWidgetEntry[]\n injectionWidgets?: ModuleInjectionWidgetEntry[]\n injectionTable?: ModuleInjectionTable\n // Optional: per-module vector search configuration (discovered from vector.ts)\n vector?: import('./vector').VectorModuleConfig\n // Optional: module-specific tenant setup configuration (from setup.ts)\n setup?: import('./setup').ModuleSetupConfig\n // Optional: default encryption maps owned by the module (from encryption.ts)\n defaultEncryptionMaps?: import('./encryption').ModuleEncryptionMap[]\n // Optional: integration marketplace declarations discovered from integration.ts\n integrations?: IntegrationDefinition[]\n bundles?: IntegrationBundle[]\n}\n\nfunction normPath(s: string) {\n return (s.startsWith('/') ? s : '/' + s).replace(/\\/+$/, '') || '/'\n}\n\n// 0 = literal (most specific), 1 = dynamic [param], 2 = catch-all [...param] or [[...param]]\nfunction segmentSpecificity(seg: string): 0 | 1 | 2 {\n if (seg.startsWith('[[...') || seg.startsWith('[...')) return 2\n if (seg.startsWith('[')) return 1\n return 0\n}\n\nfunction compareRouteSpecificity(aPattern: string, bPattern: string): number {\n const aSegs = aPattern.split('/')\n const bSegs = bPattern.split('/')\n const len = Math.max(aSegs.length, bSegs.length)\n for (let i = 0; i < len; i++) {\n const av = i < aSegs.length ? segmentSpecificity(aSegs[i]) : -1\n const bv = i < bSegs.length ? segmentSpecificity(bSegs[i]) : -1\n if (av !== bv) return av - bv\n }\n return 0\n}\n\nexport function sortRoutesBySpecificity<T extends { pattern?: string; path?: string }>(routes: T[]): T[] {\n return [...routes].sort((a, b) =>\n compareRouteSpecificity(a.pattern ?? a.path ?? '/', b.pattern ?? b.path ?? '/'),\n )\n}\n\n// Memoized per-array sorted view, so direct callers (e.g., the Next.js catch-all\n// routes that import generated `frontendRoutes`/`backendRoutes`/`apiRoutes`\n// arrays) match against a specificity-sorted view even if they never call\n// `register*RouteManifests`. Keyed by array reference; generated arrays are\n// module-level constants so this caches once per process.\nconst sortedRoutesCache = new WeakMap<object, readonly unknown[]>()\n\nfunction ensureSortedRoutes<T extends { pattern?: string; path?: string }>(routes: readonly T[]): readonly T[] {\n const cached = sortedRoutesCache.get(routes) as readonly T[] | undefined\n if (cached) return cached\n const sorted = sortRoutesBySpecificity([...routes])\n sortedRoutesCache.set(routes, sorted)\n return sorted\n}\n\nexport function matchRoutePattern(pattern: string, pathname: string): RouteMatchParams | undefined {\n const p = normPath(pattern)\n const u = normPath(pathname)\n const pSegs = p.split('/').slice(1)\n const uSegs = u.split('/').slice(1)\n const params: Record<string, string | string[]> = {}\n let i = 0\n for (let j = 0; j < pSegs.length; j++, i++) {\n const seg = pSegs[j]\n const mCatchAll = seg.match(/^\\[\\.\\.\\.(.+)\\]$/)\n const mOptCatch = seg.match(/^\\[\\[\\.\\.\\.(.+)\\]\\]$/)\n const mDyn = seg.match(/^\\[(.+)\\]$/)\n if (mCatchAll) {\n const key = mCatchAll[1]\n if (i >= uSegs.length) return undefined\n params[key] = uSegs.slice(i)\n return params\n } else if (mOptCatch) {\n const key = mOptCatch[1]\n params[key] = i < uSegs.length ? uSegs.slice(i) : []\n return params\n } else if (mDyn) {\n if (i >= uSegs.length) return undefined\n params[mDyn[1]] = uSegs[i]\n } else {\n if (i >= uSegs.length || uSegs[i].toLowerCase() !== seg.toLowerCase()) return undefined\n }\n }\n if (i !== uSegs.length) return undefined\n return params\n}\n\nfunction getPattern(r: ModuleRoute) {\n return r.pattern ?? r.path ?? '/'\n}\n\nexport function findFrontendMatch(modules: Module[], pathname: string): { route: ModuleRoute; params: Record<string, string | string[]> } | undefined {\n for (const m of modules) {\n const routes = m.frontendRoutes ?? []\n for (const r of routes) {\n const params = matchRoutePattern(getPattern(r), pathname)\n if (params) return { route: r, params }\n }\n }\n}\n\nexport function findBackendMatch(modules: Module[], pathname: string): { route: ModuleRoute; params: Record<string, string | string[]> } | undefined {\n for (const m of modules) {\n const routes = m.backendRoutes ?? []\n for (const r of routes) {\n const params = matchRoutePattern(getPattern(r), pathname)\n if (params) return { route: r, params }\n }\n }\n}\n\nexport function findApi(modules: Module[], method: HttpMethod, pathname: string): { handler: ApiHandler; params: Record<string, string | string[]>; requireAuth?: boolean; requireRoles?: string[]; metadata?: any } | undefined {\n for (const m of modules) {\n const apis = m.apis ?? []\n for (const a of apis) {\n if ('handlers' in a) {\n const params = matchRoutePattern(a.path, pathname)\n const handler = (a.handlers as any)[method]\n if (params && handler) return { handler, params, requireAuth: a.requireAuth, requireRoles: (a as any).requireRoles, metadata: (a as any).metadata }\n } else {\n const al = a as ModuleApiLegacy\n if (al.method !== method) continue\n const params = matchRoutePattern(al.path, pathname)\n if (params) {\n return { handler: al.handler, params, metadata: al.metadata }\n }\n }\n }\n }\n}\n\nexport function findRouteManifestMatch<T extends { pattern?: string; path?: string }>(\n routes: T[],\n pathname: string\n): { route: T; params: RouteMatchParams } | undefined {\n for (const route of ensureSortedRoutes(routes)) {\n const params = matchRoutePattern(route.pattern ?? route.path ?? '/', pathname)\n if (params) {\n return { route, params }\n }\n }\n}\n\nexport function findApiRouteManifestMatch<T extends { path: string; methods: HttpMethod[] }>(\n routes: T[],\n method: HttpMethod,\n pathname: string\n): { route: T; params: RouteMatchParams } | undefined {\n for (const route of ensureSortedRoutes(routes)) {\n if (!route.methods.includes(method)) continue\n const params = matchRoutePattern(route.path, pathname)\n if (params) {\n return { route, params }\n }\n }\n}\n\nexport function resolvePageRouteMetadata(pattern: string, metadata: PageMetadata | null | undefined): ModuleRouteMetadata {\n return {\n pattern: pattern || '/',\n requireAuth: metadata?.requireAuth,\n requireRoles: metadata?.requireRoles ? [...metadata.requireRoles] : undefined,\n requireFeatures: metadata?.requireFeatures ? [...metadata.requireFeatures] : undefined,\n requireCustomerAuth: metadata?.requireCustomerAuth,\n requireCustomerFeatures: metadata?.requireCustomerFeatures ? [...metadata.requireCustomerFeatures] : undefined,\n nav: metadata?.nav,\n title: metadata?.pageTitle ?? metadata?.title,\n titleKey: metadata?.pageTitleKey ?? metadata?.titleKey,\n group: metadata?.pageGroup ?? metadata?.group,\n groupKey: metadata?.pageGroupKey ?? metadata?.groupKey,\n icon: metadata?.icon,\n order: metadata?.pageOrder ?? metadata?.order,\n priority: metadata?.pagePriority ?? metadata?.priority,\n navHidden: metadata?.navHidden,\n visible: metadata?.visible,\n enabled: metadata?.enabled,\n breadcrumb: metadata?.breadcrumb,\n pageContext: metadata?.pageContext,\n placement: metadata?.placement,\n }\n}\n\nlet _backendRouteManifests: BackendRouteManifestEntry[] | null = null\n\nexport function registerBackendRouteManifests(routes: BackendRouteManifestEntry[]) {\n const pageOverrides = composePageRouteOverrides()\n const finalRoutes = Object.keys(pageOverrides).length === 0\n ? routes\n : applyPageOverridesToManifests(routes, pageOverrides, 'backend')\n _backendRouteManifests = sortRoutesBySpecificity(finalRoutes)\n}\n\nexport function getBackendRouteManifests(): BackendRouteManifestEntry[] {\n return _backendRouteManifests ?? []\n}\n\nlet _frontendRouteManifests: FrontendRouteManifestEntry[] | null = null\n\nexport function registerFrontendRouteManifests(routes: FrontendRouteManifestEntry[]) {\n const pageOverrides = composePageRouteOverrides()\n const finalRoutes = Object.keys(pageOverrides).length === 0\n ? routes\n : applyPageOverridesToManifests(routes, pageOverrides, 'frontend')\n _frontendRouteManifests = sortRoutesBySpecificity(finalRoutes)\n}\n\nexport function getFrontendRouteManifests(): FrontendRouteManifestEntry[] {\n return _frontendRouteManifests ?? []\n}\n\nlet _apiRouteManifests: ApiRouteManifestEntry[] | null = null\n\nexport function registerApiRouteManifests(routes: ApiRouteManifestEntry[]) {\n // Apply any `entry.overrides.routes.api` overrides registered through the\n // unified `modules.ts` dispatcher or programmatic API before storing the\n // manifest. The composer is cheap and returns an empty object when no\n // overrides exist, so this is a no-op for apps that do not opt in.\n const routeOverrides = composeApiRouteOverrides()\n const finalRoutes = Object.keys(routeOverrides).length === 0\n ? routes\n : applyApiOverridesToManifests(routes, routeOverrides)\n _apiRouteManifests = sortRoutesBySpecificity(finalRoutes)\n}\n\nexport function getApiRouteManifests(): ApiRouteManifestEntry[] {\n return _apiRouteManifests ?? []\n}\n\n// CLI modules registry - shared between CLI and module workers\nlet _cliModules: Module[] | null = null\n\nexport function registerCliModules(modules: Module[]) {\n if (_cliModules !== null && process.env.NODE_ENV === 'development') {\n console.debug('[Bootstrap] CLI modules re-registered (this may occur during HMR)')\n }\n _cliModules = applyModuleOverridesToModules(modules)\n}\n\nexport function getCliModules(): Module[] {\n // Return empty array if not registered - allows generate command to work without bootstrap\n return _cliModules ?? []\n}\n\nexport function hasCliModules(): boolean {\n return _cliModules !== null && _cliModules.length > 0\n}\n\nexport function getDefaultEncryptionMaps(modules: Module[]): import('./encryption').ModuleEncryptionMap[] {\n const byEntityId = new Map<string, { moduleId: string; map: import('./encryption').ModuleEncryptionMap }>()\n\n for (const mod of modules) {\n for (const entry of mod.defaultEncryptionMaps ?? []) {\n const previous = byEntityId.get(entry.entityId)\n if (previous) {\n throw new Error(\n `[registry] Duplicate default encryption map for \"${entry.entityId}\" declared by \"${previous.moduleId}\" and \"${mod.id}\"`\n )\n }\n byEntityId.set(entry.entityId, {\n moduleId: mod.id,\n map: {\n entityId: entry.entityId,\n fields: entry.fields.map((field) => ({\n field: field.field,\n hashField: field.hashField ?? null,\n })),\n },\n })\n }\n }\n\n return Array.from(byEntityId.values(), ({ map }) => map)\n}\n\nfunction ensureLazyHandler<T extends (...args: any[]) => any>(\n loaded: unknown,\n kind: 'subscriber' | 'worker',\n id: string\n): T {\n const handler = typeof loaded === 'function'\n ? loaded\n : loaded && typeof loaded === 'object' && 'default' in loaded\n ? (loaded as Record<string, unknown>).default\n : null\n if (typeof handler !== 'function') {\n throw new Error(`[registry] Invalid ${kind} module \"${id}\" (missing default export handler)`)\n }\n return handler as T\n}\n\nexport function createLazyModuleSubscriber(\n loadModule: () => Promise<unknown>,\n id: string\n): ModuleSubscriberHandler {\n let handlerPromise: Promise<ModuleSubscriberHandler> | null = null\n return async (payload, ctx) => {\n handlerPromise ??= loadModule().then((loaded) =>\n ensureLazyHandler<ModuleSubscriberHandler>(loaded, 'subscriber', id)\n )\n const handler = await handlerPromise\n return handler(payload, ctx)\n }\n}\n\nexport function createLazyModuleWorker(\n loadModule: () => Promise<unknown>,\n id: string\n): ModuleWorkerHandler {\n let handlerPromise: Promise<ModuleWorkerHandler> | null = null\n return async (job, ctx) => {\n handlerPromise ??= loadModule().then((loaded) =>\n ensureLazyHandler<ModuleWorkerHandler>(loaded, 'worker', id)\n )\n const handler = await handlerPromise\n return handler(job, ctx)\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/shared",
|
|
3
|
-
"version": "0.6.6-develop.
|
|
3
|
+
"version": "0.6.6-develop.6385.1.9a81faa5f0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
"@mikro-orm/core": "^7.1.4",
|
|
94
94
|
"@mikro-orm/decorators": "^7.1.4",
|
|
95
95
|
"@mikro-orm/postgresql": "^7.1.4",
|
|
96
|
-
"@open-mercato/cache": "0.6.6-develop.
|
|
96
|
+
"@open-mercato/cache": "0.6.6-develop.6385.1.9a81faa5f0",
|
|
97
97
|
"dotenv": "^17.4.2",
|
|
98
98
|
"rate-limiter-flexible": "^11.2.0",
|
|
99
99
|
"re2js": "2.8.3",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { parseNumberWithDefault } from '../number'
|
|
2
|
+
|
|
3
|
+
describe('parseNumberWithDefault', () => {
|
|
4
|
+
it('returns the fallback when raw is missing or blank', () => {
|
|
5
|
+
expect(parseNumberWithDefault(undefined, 5)).toBe(5)
|
|
6
|
+
expect(parseNumberWithDefault(null, 5)).toBe(5)
|
|
7
|
+
expect(parseNumberWithDefault('', 5)).toBe(5)
|
|
8
|
+
expect(parseNumberWithDefault(' ', 5)).toBe(5)
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('parses a valid numeric string', () => {
|
|
12
|
+
expect(parseNumberWithDefault('42', 0)).toBe(42)
|
|
13
|
+
expect(parseNumberWithDefault(' 42 ', 0)).toBe(42)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('allows decimals by default, but truncates when integer: true', () => {
|
|
17
|
+
expect(parseNumberWithDefault('4.5', 0)).toBe(4.5)
|
|
18
|
+
expect(parseNumberWithDefault('4.5', 0, { integer: true })).toBe(4)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('falls back to the default on a non-numeric string', () => {
|
|
22
|
+
expect(parseNumberWithDefault('not-a-number', 7)).toBe(7)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('falls back to the default when below the configured min', () => {
|
|
26
|
+
expect(parseNumberWithDefault('-1', 3, { min: 0 })).toBe(3)
|
|
27
|
+
expect(parseNumberWithDefault('0', 3, { min: 0 })).toBe(0)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('has no min by default, so negative values are accepted', () => {
|
|
31
|
+
expect(parseNumberWithDefault('-5', 0)).toBe(-5)
|
|
32
|
+
})
|
|
33
|
+
})
|