@open-mercato/core 0.6.6-develop.6384.1.f06fc0b42c → 0.6.6-develop.6386.1.391a1afb9e
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/dist/bootstrap.js +6 -1
- package/dist/bootstrap.js.map +2 -2
- package/dist/modules/attachments/lib/scope.js +26 -0
- package/dist/modules/attachments/lib/scope.js.map +7 -0
- package/dist/modules/catalog/commands/variants.js +4 -2
- package/dist/modules/catalog/commands/variants.js.map +2 -2
- package/dist/modules/configs/api/module-telemetry/route.js +75 -0
- package/dist/modules/configs/api/module-telemetry/route.js.map +7 -0
- package/dist/modules/configs/api/openapi.js +110 -0
- package/dist/modules/configs/api/openapi.js.map +2 -2
- package/dist/modules/configs/backend/config/cache/page.meta.js +1 -1
- package/dist/modules/configs/backend/config/cache/page.meta.js.map +1 -1
- package/dist/modules/configs/backend/config/module-telemetry/page.js +10 -0
- package/dist/modules/configs/backend/config/module-telemetry/page.js.map +7 -0
- package/dist/modules/configs/backend/config/module-telemetry/page.meta.js +36 -0
- package/dist/modules/configs/backend/config/module-telemetry/page.meta.js.map +7 -0
- package/dist/modules/configs/components/ModuleTelemetryPanel.js +801 -0
- package/dist/modules/configs/components/ModuleTelemetryPanel.js.map +7 -0
- package/dist/modules/messages/commands/messages.js +0 -1
- package/dist/modules/messages/commands/messages.js.map +2 -2
- package/dist/modules/messages/lib/attachments.js +13 -7
- package/dist/modules/messages/lib/attachments.js.map +2 -2
- package/dist/modules/query_index/di.js +3 -3
- package/dist/modules/query_index/di.js.map +2 -2
- package/package.json +7 -7
- package/src/bootstrap.ts +7 -1
- package/src/modules/attachments/lib/scope.ts +42 -0
- package/src/modules/catalog/commands/variants.ts +7 -2
- package/src/modules/configs/api/module-telemetry/route.ts +77 -0
- package/src/modules/configs/api/openapi.ts +117 -0
- package/src/modules/configs/backend/config/cache/page.meta.ts +1 -1
- package/src/modules/configs/backend/config/module-telemetry/page.meta.ts +34 -0
- package/src/modules/configs/backend/config/module-telemetry/page.tsx +12 -0
- package/src/modules/configs/components/ModuleTelemetryPanel.tsx +1109 -0
- package/src/modules/configs/i18n/de.json +72 -0
- package/src/modules/configs/i18n/en.json +72 -0
- package/src/modules/configs/i18n/es.json +72 -0
- package/src/modules/configs/i18n/pl.json +72 -0
- package/src/modules/messages/commands/messages.ts +0 -1
- package/src/modules/messages/lib/attachments.ts +15 -7
- package/src/modules/query_index/di.ts +3 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/messages/lib/attachments.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/core'\nimport { MESSAGE_ATTACHMENT_ENTITY_ID } from './constants'\nconst LIBRARY_ATTACHMENT_ENTITY_ID = 'attachments:library'\n\nfunction buildOrganizationScopeFilter(organizationId: string | null) {\n if (!organizationId) {\n return { organizationId: null }\n }\n return {\n $or: [\n { organizationId },\n { organizationId: null },\n ],\n }\n}\n\nexport async function linkAttachmentsToMessage(\n em: EntityManager,\n messageId: string,\n attachmentIds: string[],\n organizationId: string | null,\n tenantId: string,\n): Promise<void> {\n if (attachmentIds.length === 0) return\n\n const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')\n\n const attachments = await em.find(Attachment, {\n id: { $in: attachmentIds },\n tenantId,\n ...buildOrganizationScopeFilter(organizationId),\n })\n\n for (const attachment of attachments) {\n attachment.entityId = MESSAGE_ATTACHMENT_ENTITY_ID\n attachment.recordId = messageId\n }\n\n await em.flush()\n}\n\nexport async function getMessageAttachments(\n em: EntityManager,\n messageId: string,\n organizationId: string | null,\n tenantId: string,\n): Promise<Array<{\n id: string\n fileName: string\n fileSize: number\n mimeType: string\n url: string\n}>> {\n const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')\n\n const attachments = await em.find(Attachment, {\n entityId: MESSAGE_ATTACHMENT_ENTITY_ID,\n recordId: messageId,\n tenantId,\n ...buildOrganizationScopeFilter(organizationId),\n })\n\n return attachments.map((attachment) => ({\n id: attachment.id,\n fileName: attachment.fileName,\n fileSize: attachment.fileSize,\n mimeType: attachment.mimeType,\n url: attachment.url,\n }))\n}\n\nexport type MessageEmailAttachment = {\n fileName: string\n fileSize: number\n mimeType: string\n partitionCode: string\n storagePath: string\n storageDriver: string\n}\n\nexport async function getMessageEmailAttachments(\n em: EntityManager,\n messageId: string,\n organizationId: string | null,\n tenantId: string,\n): Promise<MessageEmailAttachment[]> {\n const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')\n\n const attachments = await em.find(Attachment, {\n entityId: MESSAGE_ATTACHMENT_ENTITY_ID,\n recordId: messageId,\n tenantId,\n ...buildOrganizationScopeFilter(organizationId),\n })\n\n return attachments.map((attachment) => ({\n fileName: attachment.fileName,\n fileSize: attachment.fileSize,\n mimeType: attachment.mimeType,\n partitionCode: attachment.partitionCode,\n storagePath: attachment.storagePath,\n storageDriver: attachment.storageDriver,\n }))\n}\n\nexport async function linkLibraryAttachmentsToMessage(\n em: EntityManager,\n messageId: string,\n sourceRecordId: string,\n organizationId: string | null,\n tenantId: string,\n): Promise<void> {\n if (!sourceRecordId.trim()) return\n\n const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')\n\n const attachments = await em.find(Attachment, {\n entityId: LIBRARY_ATTACHMENT_ENTITY_ID,\n recordId: sourceRecordId,\n tenantId,\n ...buildOrganizationScopeFilter(organizationId),\n })\n\n if (!attachments.length) return\n\n for (const attachment of attachments) {\n attachment.entityId = MESSAGE_ATTACHMENT_ENTITY_ID\n attachment.recordId = messageId\n }\n\n await em.flush()\n}\n\nexport async function copyAttachmentsForForward(\n em: EntityManager,\n sourceMessageId: string,\n targetMessageId: string,\n
|
|
5
|
-
"mappings": "AACA,SAAS,oCAAoC;AAC7C,MAAM,+BAA+B;AAErC,SAAS,6BAA6B,gBAA+B;AACnE,MAAI,CAAC,gBAAgB;AACnB,WAAO,EAAE,gBAAgB,KAAK;AAAA,EAChC;AACA,SAAO;AAAA,IACL,KAAK;AAAA,MACH,EAAE,eAAe;AAAA,MACjB,EAAE,gBAAgB,KAAK;AAAA,IACzB;AAAA,EACF;AACF;AAEA,eAAsB,yBACpB,IACA,WACA,eACA,gBACA,UACe;AACf,MAAI,cAAc,WAAW,EAAG;AAEhC,QAAM,EAAE,WAAW,IAAI,MAAM,OAAO,sDAAsD;AAE1F,QAAM,cAAc,MAAM,GAAG,KAAK,YAAY;AAAA,IAC5C,IAAI,EAAE,KAAK,cAAc;AAAA,IACzB;AAAA,IACA,GAAG,6BAA6B,cAAc;AAAA,EAChD,CAAC;AAED,aAAW,cAAc,aAAa;AACpC,eAAW,WAAW;AACtB,eAAW,WAAW;AAAA,EACxB;AAEA,QAAM,GAAG,MAAM;AACjB;AAEA,eAAsB,sBACpB,IACA,WACA,gBACA,UAOE;AACF,QAAM,EAAE,WAAW,IAAI,MAAM,OAAO,sDAAsD;AAE1F,QAAM,cAAc,MAAM,GAAG,KAAK,YAAY;AAAA,IAC5C,UAAU;AAAA,IACV,UAAU;AAAA,IACV;AAAA,IACA,GAAG,6BAA6B,cAAc;AAAA,EAChD,CAAC;AAED,SAAO,YAAY,IAAI,CAAC,gBAAgB;AAAA,IACtC,IAAI,WAAW;AAAA,IACf,UAAU,WAAW;AAAA,IACrB,UAAU,WAAW;AAAA,IACrB,UAAU,WAAW;AAAA,IACrB,KAAK,WAAW;AAAA,EAClB,EAAE;AACJ;AAWA,eAAsB,2BACpB,IACA,WACA,gBACA,UACmC;AACnC,QAAM,EAAE,WAAW,IAAI,MAAM,OAAO,sDAAsD;AAE1F,QAAM,cAAc,MAAM,GAAG,KAAK,YAAY;AAAA,IAC5C,UAAU;AAAA,IACV,UAAU;AAAA,IACV;AAAA,IACA,GAAG,6BAA6B,cAAc;AAAA,EAChD,CAAC;AAED,SAAO,YAAY,IAAI,CAAC,gBAAgB;AAAA,IACtC,UAAU,WAAW;AAAA,IACrB,UAAU,WAAW;AAAA,IACrB,UAAU,WAAW;AAAA,IACrB,eAAe,WAAW;AAAA,IAC1B,aAAa,WAAW;AAAA,IACxB,eAAe,WAAW;AAAA,EAC5B,EAAE;AACJ;AAEA,eAAsB,gCACpB,IACA,WACA,gBACA,gBACA,UACe;AACf,MAAI,CAAC,eAAe,KAAK,EAAG;AAE5B,QAAM,EAAE,WAAW,IAAI,MAAM,OAAO,sDAAsD;AAE1F,QAAM,cAAc,MAAM,GAAG,KAAK,YAAY;AAAA,IAC5C,UAAU;AAAA,IACV,UAAU;AAAA,IACV;AAAA,IACA,GAAG,6BAA6B,cAAc;AAAA,EAChD,CAAC;AAED,MAAI,CAAC,YAAY,OAAQ;AAEzB,aAAW,cAAc,aAAa;AACpC,eAAW,WAAW;AACtB,eAAW,WAAW;AAAA,EACxB;AAEA,QAAM,GAAG,MAAM;AACjB;AAEA,eAAsB,0BACpB,IACA,iBACA,iBACA,
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/core'\nimport { resolveAttachmentScopePair } from '@open-mercato/core/modules/attachments/lib/scope'\nimport { MESSAGE_ATTACHMENT_ENTITY_ID } from './constants'\nconst LIBRARY_ATTACHMENT_ENTITY_ID = 'attachments:library'\n\nfunction buildOrganizationScopeFilter(organizationId: string | null) {\n if (!organizationId) {\n return { organizationId: null }\n }\n return {\n $or: [\n { organizationId },\n { organizationId: null },\n ],\n }\n}\n\nexport async function linkAttachmentsToMessage(\n em: EntityManager,\n messageId: string,\n attachmentIds: string[],\n organizationId: string | null,\n tenantId: string,\n): Promise<void> {\n if (attachmentIds.length === 0) return\n\n const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')\n\n const attachments = await em.find(Attachment, {\n id: { $in: attachmentIds },\n tenantId,\n ...buildOrganizationScopeFilter(organizationId),\n })\n\n for (const attachment of attachments) {\n attachment.entityId = MESSAGE_ATTACHMENT_ENTITY_ID\n attachment.recordId = messageId\n }\n\n await em.flush()\n}\n\nexport async function getMessageAttachments(\n em: EntityManager,\n messageId: string,\n organizationId: string | null,\n tenantId: string,\n): Promise<Array<{\n id: string\n fileName: string\n fileSize: number\n mimeType: string\n url: string\n}>> {\n const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')\n\n const attachments = await em.find(Attachment, {\n entityId: MESSAGE_ATTACHMENT_ENTITY_ID,\n recordId: messageId,\n tenantId,\n ...buildOrganizationScopeFilter(organizationId),\n })\n\n return attachments.map((attachment) => ({\n id: attachment.id,\n fileName: attachment.fileName,\n fileSize: attachment.fileSize,\n mimeType: attachment.mimeType,\n url: attachment.url,\n }))\n}\n\nexport type MessageEmailAttachment = {\n fileName: string\n fileSize: number\n mimeType: string\n partitionCode: string\n storagePath: string\n storageDriver: string\n}\n\nexport async function getMessageEmailAttachments(\n em: EntityManager,\n messageId: string,\n organizationId: string | null,\n tenantId: string,\n): Promise<MessageEmailAttachment[]> {\n const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')\n\n const attachments = await em.find(Attachment, {\n entityId: MESSAGE_ATTACHMENT_ENTITY_ID,\n recordId: messageId,\n tenantId,\n ...buildOrganizationScopeFilter(organizationId),\n })\n\n return attachments.map((attachment) => ({\n fileName: attachment.fileName,\n fileSize: attachment.fileSize,\n mimeType: attachment.mimeType,\n partitionCode: attachment.partitionCode,\n storagePath: attachment.storagePath,\n storageDriver: attachment.storageDriver,\n }))\n}\n\nexport async function linkLibraryAttachmentsToMessage(\n em: EntityManager,\n messageId: string,\n sourceRecordId: string,\n organizationId: string | null,\n tenantId: string,\n): Promise<void> {\n if (!sourceRecordId.trim()) return\n\n const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')\n\n const attachments = await em.find(Attachment, {\n entityId: LIBRARY_ATTACHMENT_ENTITY_ID,\n recordId: sourceRecordId,\n tenantId,\n ...buildOrganizationScopeFilter(organizationId),\n })\n\n if (!attachments.length) return\n\n for (const attachment of attachments) {\n attachment.entityId = MESSAGE_ATTACHMENT_ENTITY_ID\n attachment.recordId = messageId\n }\n\n await em.flush()\n}\n\nexport async function copyAttachmentsForForward(\n em: EntityManager,\n sourceMessageId: string,\n targetMessageId: string,\n tenantId: string,\n): Promise<number> {\n return copyAttachmentsForForwardMessages(\n em,\n [sourceMessageId],\n targetMessageId,\n tenantId,\n )\n}\n\nexport async function copyAttachmentsForForwardMessages(\n em: EntityManager,\n sourceMessageIds: string[],\n targetMessageId: string,\n tenantId: string,\n): Promise<number> {\n if (sourceMessageIds.length === 0) return 0\n\n const { Attachment } = await import('@open-mercato/core/modules/attachments/data/entities')\n const sourceAttachments = await em.find(Attachment, {\n entityId: MESSAGE_ATTACHMENT_ENTITY_ID,\n recordId: { $in: sourceMessageIds },\n tenantId,\n })\n if (sourceAttachments.length === 0) {\n return 0\n }\n\n const dedupedById = new Map<string, typeof sourceAttachments[number]>()\n for (const sourceAttachment of sourceAttachments) {\n if (!dedupedById.has(sourceAttachment.id)) {\n dedupedById.set(sourceAttachment.id, sourceAttachment)\n }\n }\n\n let copied = 0\n for (const sourceAttachment of dedupedById.values()) {\n // Carry the source attachment's scope across as a unit so a forwarded copy\n // can never become a partial-null row. Source rows that are themselves\n // partial-null are unreadable dead data; skip rather than propagate them.\n const scope = resolveAttachmentScopePair(sourceAttachment)\n if (!scope) continue\n\n const copy = em.create(Attachment, {\n entityId: MESSAGE_ATTACHMENT_ENTITY_ID,\n recordId: targetMessageId,\n organizationId: scope.organizationId,\n tenantId: scope.tenantId,\n fileName: sourceAttachment.fileName,\n mimeType: sourceAttachment.mimeType,\n fileSize: sourceAttachment.fileSize,\n storageDriver: sourceAttachment.storageDriver,\n storagePath: sourceAttachment.storagePath,\n storageMetadata: sourceAttachment.storageMetadata,\n url: sourceAttachment.url,\n partitionCode: sourceAttachment.partitionCode,\n })\n\n em.persist(copy)\n copied += 1\n }\n\n if (copied > 0) {\n await em.flush()\n }\n return copied\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -144,9 +144,9 @@ function register(container) {
|
|
|
144
144
|
for (const entityType of Array.from(new Set(ids))) {
|
|
145
145
|
const [mod, ent] = entityType.split(":");
|
|
146
146
|
if (!mod || !ent) continue;
|
|
147
|
-
bus.on(`${mod}.${ent}.created`, makeUpsertHandler(entityType));
|
|
148
|
-
bus.on(`${mod}.${ent}.updated`, makeUpsertHandler(entityType));
|
|
149
|
-
bus.on(`${mod}.${ent}.deleted`, makeDeleteHandler(entityType));
|
|
147
|
+
bus.on(`${mod}.${ent}.created`, makeUpsertHandler(entityType), { moduleId: "query_index" });
|
|
148
|
+
bus.on(`${mod}.${ent}.updated`, makeUpsertHandler(entityType), { moduleId: "query_index" });
|
|
149
|
+
bus.on(`${mod}.${ent}.deleted`, makeDeleteHandler(entityType), { moduleId: "query_index" });
|
|
150
150
|
}
|
|
151
151
|
};
|
|
152
152
|
if (cfEntityIds.length > 0) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/query_index/di.ts"],
|
|
4
|
-
"sourcesContent": ["import type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { BasicQueryEngine, resolveEntityTableName } from '@open-mercato/shared/lib/query/engine'\nimport { HybridQueryEngine } from './lib/engine'\nimport { markDeleted } from './lib/indexer'\nimport type { EventBus } from '@open-mercato/events'\nimport type { VectorIndexService } from '@open-mercato/search/vector'\n\nfunction toEntityTypeFromEvent(event: string): string | null {\n // Expect '<module>.<entity>.<action>'\n const parts = event.split('.')\n if (parts.length !== 3) return null\n const [mod, ent] = parts\n return `${mod}:${ent}`\n}\n\nexport function register(container: AppContainer) {\n // Override queryEngine with hybrid that prefers JSONB index when available\n try {\n const em = (container.resolve('em') as any)\n const basic = new BasicQueryEngine(\n em,\n undefined,\n () => {\n try {\n return container.resolve('tenantEncryptionService') as any\n } catch {\n return null\n }\n },\n )\n const hybrid = new HybridQueryEngine(\n em,\n basic,\n () => {\n try {\n return (container.resolve('eventBus') as EventBus)\n } catch {\n return null\n }\n },\n () => {\n try {\n return (container.resolve('vectorIndexService') as VectorIndexService)\n } catch {\n return null\n }\n },\n () => {\n try {\n return container.resolve('tenantEncryptionService') as any\n } catch {\n return null\n }\n },\n )\n // Replace existing registration\n ;(container as any).register({ queryEngine: { resolve: () => hybrid } })\n } catch {}\n\n // Subscribe to CRUD events and forward to query_index subscribers for unified handling\n const setup = () => {\n let bus: any\n try { bus = (container.resolve('eventBus') as any) } catch { bus = null }\n if (!bus) { setTimeout(setup, 0); return }\n\n const makeUpsertHandler = (entityType: string) => async (payload: any, ctx: any) => {\n try {\n const em = ctx.resolve('em')\n let orgId = payload?.organizationId || payload?.orgId || ctx?.organizationId || null\n let tenantId = payload?.tenantId || ctx?.tenantId || null\n const id = String(payload?.id || payload?.recordId || '')\n if (!id) return\n if (!orgId || !tenantId) {\n try {\n const db = (em as any).getKysely()\n const table = resolveEntityTableName(em, entityType)\n const row = await db\n .selectFrom(table as any)\n .select(['organization_id' as any, 'tenant_id' as any])\n .where('id' as any, '=', id)\n .executeTakeFirst() as { organization_id: string | null; tenant_id: string | null } | undefined\n orgId = row?.organization_id ?? orgId\n tenantId = row?.tenant_id ?? tenantId\n } catch {}\n }\n // Optional: only index when custom field definitions exist for this entity (org/global)\n try {\n const db = (em as any).getKysely()\n let cfQuery = db\n .selectFrom('custom_field_defs' as any)\n .select(['id' as any])\n .where('entity_id' as any, '=', entityType)\n .where('is_active' as any, '=', true)\n if (orgId != null) {\n cfQuery = cfQuery.where((eb: any) => eb.or([\n eb('organization_id' as any, '=', orgId),\n eb('organization_id' as any, 'is', null),\n ]))\n } else {\n cfQuery = cfQuery.where('organization_id' as any, 'is', null as any)\n }\n if (tenantId != null) {\n cfQuery = cfQuery.where((eb: any) => eb.or([\n eb('tenant_id' as any, '=', tenantId),\n eb('tenant_id' as any, 'is', null),\n ]))\n } else {\n cfQuery = cfQuery.where('tenant_id' as any, 'is', null as any)\n }\n const hasCf = await cfQuery.executeTakeFirst()\n if (!hasCf) return\n } catch {}\n try {\n const bus = ctx.resolve('eventBus') as any\n await bus.emitEvent('query_index.upsert_one', { entityType, recordId: id, organizationId: orgId, tenantId })\n } catch {}\n } catch {}\n }\n const makeDeleteHandler = (entityType: string) => async (payload: any, ctx: any) => {\n try {\n const em = ctx.resolve('em')\n let orgId = payload?.organizationId || payload?.orgId || ctx?.organizationId || null\n let tenantId = payload?.tenantId || ctx?.tenantId || null\n const id = String(payload?.id || payload?.recordId || '')\n if (!id) return\n if (!orgId || !tenantId) {\n try {\n const db = (em as any).getKysely()\n const table = resolveEntityTableName(em, entityType)\n const row = await db\n .selectFrom(table as any)\n .select(['organization_id' as any, 'tenant_id' as any])\n .where('id' as any, '=', id)\n .executeTakeFirst() as { organization_id: string | null; tenant_id: string | null } | undefined\n orgId = row?.organization_id ?? orgId\n tenantId = row?.tenant_id ?? tenantId\n } catch {}\n }\n try {\n const bus = ctx.resolve('eventBus') as any\n await bus.emitEvent('query_index.delete_one', { entityType, recordId: id, organizationId: orgId, tenantId })\n } catch {}\n } catch {}\n }\n\n // Build list of entity ids to subscribe to\n try {\n const em = (container.resolve('em') as any)\n const db = (em as any).getKysely()\n const cfEntityIds: string[] = []\n db\n .selectFrom('custom_field_defs' as any)\n .select(['entity_id' as any])\n .distinct()\n .execute()\n .then((rows: any[]) => {\n for (const r of rows || []) cfEntityIds.push(String(r.entity_id))\n })\n .catch(() => {})\n .finally(() => {\n const proceed = (ids: string[]) => {\n for (const entityType of Array.from(new Set(ids))) {\n const [mod, ent] = entityType.split(':')\n if (!mod || !ent) continue\n bus.on(`${mod}.${ent}.created`, makeUpsertHandler(entityType))\n bus.on(`${mod}.${ent}.updated`, makeUpsertHandler(entityType))\n bus.on(`${mod}.${ent}.deleted`, makeDeleteHandler(entityType))\n }\n }\n if (cfEntityIds.length > 0) {\n proceed(cfEntityIds)\n } else {\n // Fallback to generated entity ids without await\n import('#generated/entities.ids.generated').then((core) => {\n const flatten = (E: any): string[] => Object.values(E || {}).flatMap((o: any) => Object.values(o || {}) as string[])\n const guesses = new Set<string>([...flatten((core as any).E)])\n proceed(Array.from(guesses))\n }).catch(() => {})\n }\n })\n } catch {}\n }\n\n try { setup() } catch {}\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import type { AppContainer } from '@open-mercato/shared/lib/di/container'\nimport { BasicQueryEngine, resolveEntityTableName } from '@open-mercato/shared/lib/query/engine'\nimport { HybridQueryEngine } from './lib/engine'\nimport { markDeleted } from './lib/indexer'\nimport type { EventBus } from '@open-mercato/events'\nimport type { VectorIndexService } from '@open-mercato/search/vector'\n\nfunction toEntityTypeFromEvent(event: string): string | null {\n // Expect '<module>.<entity>.<action>'\n const parts = event.split('.')\n if (parts.length !== 3) return null\n const [mod, ent] = parts\n return `${mod}:${ent}`\n}\n\nexport function register(container: AppContainer) {\n // Override queryEngine with hybrid that prefers JSONB index when available\n try {\n const em = (container.resolve('em') as any)\n const basic = new BasicQueryEngine(\n em,\n undefined,\n () => {\n try {\n return container.resolve('tenantEncryptionService') as any\n } catch {\n return null\n }\n },\n )\n const hybrid = new HybridQueryEngine(\n em,\n basic,\n () => {\n try {\n return (container.resolve('eventBus') as EventBus)\n } catch {\n return null\n }\n },\n () => {\n try {\n return (container.resolve('vectorIndexService') as VectorIndexService)\n } catch {\n return null\n }\n },\n () => {\n try {\n return container.resolve('tenantEncryptionService') as any\n } catch {\n return null\n }\n },\n )\n // Replace existing registration\n ;(container as any).register({ queryEngine: { resolve: () => hybrid } })\n } catch {}\n\n // Subscribe to CRUD events and forward to query_index subscribers for unified handling\n const setup = () => {\n let bus: any\n try { bus = (container.resolve('eventBus') as any) } catch { bus = null }\n if (!bus) { setTimeout(setup, 0); return }\n\n const makeUpsertHandler = (entityType: string) => async (payload: any, ctx: any) => {\n try {\n const em = ctx.resolve('em')\n let orgId = payload?.organizationId || payload?.orgId || ctx?.organizationId || null\n let tenantId = payload?.tenantId || ctx?.tenantId || null\n const id = String(payload?.id || payload?.recordId || '')\n if (!id) return\n if (!orgId || !tenantId) {\n try {\n const db = (em as any).getKysely()\n const table = resolveEntityTableName(em, entityType)\n const row = await db\n .selectFrom(table as any)\n .select(['organization_id' as any, 'tenant_id' as any])\n .where('id' as any, '=', id)\n .executeTakeFirst() as { organization_id: string | null; tenant_id: string | null } | undefined\n orgId = row?.organization_id ?? orgId\n tenantId = row?.tenant_id ?? tenantId\n } catch {}\n }\n // Optional: only index when custom field definitions exist for this entity (org/global)\n try {\n const db = (em as any).getKysely()\n let cfQuery = db\n .selectFrom('custom_field_defs' as any)\n .select(['id' as any])\n .where('entity_id' as any, '=', entityType)\n .where('is_active' as any, '=', true)\n if (orgId != null) {\n cfQuery = cfQuery.where((eb: any) => eb.or([\n eb('organization_id' as any, '=', orgId),\n eb('organization_id' as any, 'is', null),\n ]))\n } else {\n cfQuery = cfQuery.where('organization_id' as any, 'is', null as any)\n }\n if (tenantId != null) {\n cfQuery = cfQuery.where((eb: any) => eb.or([\n eb('tenant_id' as any, '=', tenantId),\n eb('tenant_id' as any, 'is', null),\n ]))\n } else {\n cfQuery = cfQuery.where('tenant_id' as any, 'is', null as any)\n }\n const hasCf = await cfQuery.executeTakeFirst()\n if (!hasCf) return\n } catch {}\n try {\n const bus = ctx.resolve('eventBus') as any\n await bus.emitEvent('query_index.upsert_one', { entityType, recordId: id, organizationId: orgId, tenantId })\n } catch {}\n } catch {}\n }\n const makeDeleteHandler = (entityType: string) => async (payload: any, ctx: any) => {\n try {\n const em = ctx.resolve('em')\n let orgId = payload?.organizationId || payload?.orgId || ctx?.organizationId || null\n let tenantId = payload?.tenantId || ctx?.tenantId || null\n const id = String(payload?.id || payload?.recordId || '')\n if (!id) return\n if (!orgId || !tenantId) {\n try {\n const db = (em as any).getKysely()\n const table = resolveEntityTableName(em, entityType)\n const row = await db\n .selectFrom(table as any)\n .select(['organization_id' as any, 'tenant_id' as any])\n .where('id' as any, '=', id)\n .executeTakeFirst() as { organization_id: string | null; tenant_id: string | null } | undefined\n orgId = row?.organization_id ?? orgId\n tenantId = row?.tenant_id ?? tenantId\n } catch {}\n }\n try {\n const bus = ctx.resolve('eventBus') as any\n await bus.emitEvent('query_index.delete_one', { entityType, recordId: id, organizationId: orgId, tenantId })\n } catch {}\n } catch {}\n }\n\n // Build list of entity ids to subscribe to\n try {\n const em = (container.resolve('em') as any)\n const db = (em as any).getKysely()\n const cfEntityIds: string[] = []\n db\n .selectFrom('custom_field_defs' as any)\n .select(['entity_id' as any])\n .distinct()\n .execute()\n .then((rows: any[]) => {\n for (const r of rows || []) cfEntityIds.push(String(r.entity_id))\n })\n .catch(() => {})\n .finally(() => {\n const proceed = (ids: string[]) => {\n for (const entityType of Array.from(new Set(ids))) {\n const [mod, ent] = entityType.split(':')\n if (!mod || !ent) continue\n bus.on(`${mod}.${ent}.created`, makeUpsertHandler(entityType), { moduleId: 'query_index' })\n bus.on(`${mod}.${ent}.updated`, makeUpsertHandler(entityType), { moduleId: 'query_index' })\n bus.on(`${mod}.${ent}.deleted`, makeDeleteHandler(entityType), { moduleId: 'query_index' })\n }\n }\n if (cfEntityIds.length > 0) {\n proceed(cfEntityIds)\n } else {\n // Fallback to generated entity ids without await\n import('#generated/entities.ids.generated').then((core) => {\n const flatten = (E: any): string[] => Object.values(E || {}).flatMap((o: any) => Object.values(o || {}) as string[])\n const guesses = new Set<string>([...flatten((core as any).E)])\n proceed(Array.from(guesses))\n }).catch(() => {})\n }\n })\n } catch {}\n }\n\n try { setup() } catch {}\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["bus"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.6.6-develop.
|
|
3
|
+
"version": "0.6.6-develop.6386.1.391a1afb9e",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -246,16 +246,16 @@
|
|
|
246
246
|
"zod": "^4.4.3"
|
|
247
247
|
},
|
|
248
248
|
"peerDependencies": {
|
|
249
|
-
"@open-mercato/ai-assistant": "0.6.6-develop.
|
|
250
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
251
|
-
"@open-mercato/ui": "0.6.6-develop.
|
|
249
|
+
"@open-mercato/ai-assistant": "0.6.6-develop.6386.1.391a1afb9e",
|
|
250
|
+
"@open-mercato/shared": "0.6.6-develop.6386.1.391a1afb9e",
|
|
251
|
+
"@open-mercato/ui": "0.6.6-develop.6386.1.391a1afb9e",
|
|
252
252
|
"react": "^19.0.0",
|
|
253
253
|
"react-dom": "^19.0.0"
|
|
254
254
|
},
|
|
255
255
|
"devDependencies": {
|
|
256
|
-
"@open-mercato/ai-assistant": "0.6.6-develop.
|
|
257
|
-
"@open-mercato/shared": "0.6.6-develop.
|
|
258
|
-
"@open-mercato/ui": "0.6.6-develop.
|
|
256
|
+
"@open-mercato/ai-assistant": "0.6.6-develop.6386.1.391a1afb9e",
|
|
257
|
+
"@open-mercato/shared": "0.6.6-develop.6386.1.391a1afb9e",
|
|
258
|
+
"@open-mercato/ui": "0.6.6-develop.6386.1.391a1afb9e",
|
|
259
259
|
"@testing-library/dom": "^10.4.1",
|
|
260
260
|
"@testing-library/jest-dom": "^6.9.1",
|
|
261
261
|
"@testing-library/react": "^16.3.1",
|
package/src/bootstrap.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AwilixContainer } from 'awilix'
|
|
2
2
|
import { asValue } from 'awilix'
|
|
3
|
+
import type { ModuleSubscriber } from '@open-mercato/shared/modules/registry'
|
|
3
4
|
import { createEventBus } from '@open-mercato/events/index'
|
|
4
5
|
import { setGlobalEventBus } from '@open-mercato/shared/modules/events'
|
|
5
6
|
import { createCacheService } from '@open-mercato/cache'
|
|
@@ -150,7 +151,12 @@ export async function bootstrap(container: AwilixContainer) {
|
|
|
150
151
|
const { getModules } = await import('@open-mercato/shared/lib/i18n/server')
|
|
151
152
|
loadedModules = getModules()
|
|
152
153
|
} catch {}
|
|
153
|
-
const subs = loadedModules.flatMap((m) =>
|
|
154
|
+
const subs = loadedModules.flatMap((m) =>
|
|
155
|
+
(m.subscribers || []).map((subscriber: ModuleSubscriber) => ({
|
|
156
|
+
...subscriber,
|
|
157
|
+
moduleId: subscriber.moduleId ?? m.id,
|
|
158
|
+
})),
|
|
159
|
+
)
|
|
154
160
|
if (subs.length) (container.resolve as any)('eventBus').registerModuleSubscribers(subs)
|
|
155
161
|
|
|
156
162
|
// Extract sync subscribers and register in the sync-subscriber-store
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type AttachmentScopePair = {
|
|
2
|
+
organizationId: string | null
|
|
3
|
+
tenantId: string | null
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export type AttachmentScopeCandidate = {
|
|
7
|
+
organizationId?: string | null
|
|
8
|
+
tenantId?: string | null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalizeScopeValue(value: string | null | undefined): string | null {
|
|
12
|
+
if (typeof value !== 'string') return value ?? null
|
|
13
|
+
const trimmed = value.trim()
|
|
14
|
+
return trimmed.length > 0 ? trimmed : null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isValidAttachmentScopePair(candidate: AttachmentScopeCandidate): boolean {
|
|
18
|
+
const organizationId = normalizeScopeValue(candidate.organizationId)
|
|
19
|
+
const tenantId = normalizeScopeValue(candidate.tenantId)
|
|
20
|
+
return (organizationId === null) === (tenantId === null)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Returns the first candidate whose tenant/organization columns form a valid
|
|
25
|
+
* "both set or both null" scope pair (blank/whitespace treated as null), or
|
|
26
|
+
* `null` when no candidate satisfies the invariant. Copy/clone sites use this
|
|
27
|
+
* to carry a scope pair across as a unit instead of coalescing the two columns
|
|
28
|
+
* independently, which is what allows a partial-null row to be constructed.
|
|
29
|
+
*/
|
|
30
|
+
export function resolveAttachmentScopePair(
|
|
31
|
+
...candidates: Array<AttachmentScopeCandidate | null | undefined>
|
|
32
|
+
): AttachmentScopePair | null {
|
|
33
|
+
for (const candidate of candidates) {
|
|
34
|
+
if (!candidate) continue
|
|
35
|
+
const organizationId = normalizeScopeValue(candidate.organizationId)
|
|
36
|
+
const tenantId = normalizeScopeValue(candidate.tenantId)
|
|
37
|
+
if ((organizationId === null) === (tenantId === null)) {
|
|
38
|
+
return { organizationId, tenantId }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
} from '../data/entities'
|
|
20
20
|
import type { DataEngine } from '@open-mercato/shared/lib/data/engine'
|
|
21
21
|
import { Attachment } from '@open-mercato/core/modules/attachments/data/entities'
|
|
22
|
+
import { resolveAttachmentScopePair } from '@open-mercato/core/modules/attachments/lib/scope'
|
|
22
23
|
import {
|
|
23
24
|
variantCreateSchema,
|
|
24
25
|
variantUpdateSchema,
|
|
@@ -562,11 +563,15 @@ async function aggregateVariantMediaToProduct(
|
|
|
562
563
|
for (const source of attachments) {
|
|
563
564
|
const key = buildKey(source)
|
|
564
565
|
if (existingKeys.has(key)) continue
|
|
566
|
+
// Carry a scope pair across as a unit (source row first, else the variant
|
|
567
|
+
// scope, else global) so independent `??` coalescing can't produce a
|
|
568
|
+
// partial-null attachment row.
|
|
569
|
+
const scope = resolveAttachmentScopePair(source, variant) ?? { organizationId: null, tenantId: null }
|
|
565
570
|
const clone = em.create(Attachment, {
|
|
566
571
|
entityId: E.catalog.catalog_product,
|
|
567
572
|
recordId: productId,
|
|
568
|
-
organizationId:
|
|
569
|
-
tenantId:
|
|
573
|
+
organizationId: scope.organizationId,
|
|
574
|
+
tenantId: scope.tenantId,
|
|
570
575
|
partitionCode: source.partitionCode,
|
|
571
576
|
fileName: source.fileName,
|
|
572
577
|
mimeType: source.mimeType,
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
3
|
+
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
4
|
+
import {
|
|
5
|
+
clearModuleResourceUsageData,
|
|
6
|
+
getModuleResourceUsageReport,
|
|
7
|
+
} from '@open-mercato/shared/lib/modules/resource-usage'
|
|
8
|
+
import {
|
|
9
|
+
configErrorSchema,
|
|
10
|
+
configsTag,
|
|
11
|
+
moduleTelemetryClearResponseSchema,
|
|
12
|
+
moduleTelemetryResponseSchema,
|
|
13
|
+
} from '../openapi'
|
|
14
|
+
|
|
15
|
+
export const metadata = {
|
|
16
|
+
GET: { requireAuth: true, requireFeatures: ['configs.system_status.view'] },
|
|
17
|
+
// Clearing telemetry shouldn't itself perturb the very telemetry it just cleared.
|
|
18
|
+
DELETE: { requireAuth: true, requireFeatures: ['configs.manage'], skipModuleResourceUsageTracking: true },
|
|
19
|
+
} as const
|
|
20
|
+
|
|
21
|
+
function canClearModuleTelemetry(): boolean {
|
|
22
|
+
return process.env.NODE_ENV === 'development'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function GET(req: Request) {
|
|
26
|
+
const auth = await getAuthFromRequest(req)
|
|
27
|
+
if (!auth?.sub) {
|
|
28
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return NextResponse.json({
|
|
32
|
+
...getModuleResourceUsageReport(),
|
|
33
|
+
canClearTelemetry: canClearModuleTelemetry(),
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function DELETE(req: Request) {
|
|
38
|
+
const auth = await getAuthFromRequest(req)
|
|
39
|
+
if (!auth?.sub) {
|
|
40
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!canClearModuleTelemetry()) {
|
|
44
|
+
return NextResponse.json({ error: 'Clearing module telemetry is only available in development mode.' }, { status: 403 })
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
clearModuleResourceUsageData()
|
|
48
|
+
return NextResponse.json({ cleared: true })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const openApi: OpenApiRouteDoc = {
|
|
52
|
+
tag: configsTag,
|
|
53
|
+
summary: 'Module telemetry usage',
|
|
54
|
+
methods: {
|
|
55
|
+
GET: {
|
|
56
|
+
summary: 'Get module resource usage telemetry',
|
|
57
|
+
description: 'Returns in-process module resource attribution for API routes, event subscribers, and queue workers.',
|
|
58
|
+
responses: [
|
|
59
|
+
{ status: 200, description: 'Module resource usage report', schema: moduleTelemetryResponseSchema },
|
|
60
|
+
],
|
|
61
|
+
errors: [
|
|
62
|
+
{ status: 401, description: 'Unauthorized', schema: configErrorSchema },
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
DELETE: {
|
|
66
|
+
summary: 'Clear module telemetry data',
|
|
67
|
+
description: 'Development-only endpoint that clears in-memory module telemetry and local process telemetry files.',
|
|
68
|
+
responses: [
|
|
69
|
+
{ status: 200, description: 'Module telemetry cleared', schema: moduleTelemetryClearResponseSchema },
|
|
70
|
+
],
|
|
71
|
+
errors: [
|
|
72
|
+
{ status: 401, description: 'Unauthorized', schema: configErrorSchema },
|
|
73
|
+
{ status: 403, description: 'Forbidden outside development mode', schema: configErrorSchema },
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
}
|
|
@@ -120,3 +120,120 @@ export const executeUpgradeActionResponseSchema = z.object({
|
|
|
120
120
|
message: z.string().describe('Localized success message'),
|
|
121
121
|
version: z.string().describe('Application version'),
|
|
122
122
|
})
|
|
123
|
+
|
|
124
|
+
const moduleResourceSurfaceSchema = z.enum(['api', 'subscriber', 'worker', 'custom'])
|
|
125
|
+
|
|
126
|
+
const moduleResourceUsageEntrySchema = z.object({
|
|
127
|
+
moduleId: z.string(),
|
|
128
|
+
surface: moduleResourceSurfaceSchema,
|
|
129
|
+
operation: z.string(),
|
|
130
|
+
resourceId: z.string().nullable(),
|
|
131
|
+
calls: z.number().int(),
|
|
132
|
+
errors: z.number().int(),
|
|
133
|
+
totalDurationMs: z.number(),
|
|
134
|
+
maxDurationMs: z.number(),
|
|
135
|
+
p95DurationMs: z.number(),
|
|
136
|
+
totalCpuUserMs: z.number(),
|
|
137
|
+
totalCpuSystemMs: z.number(),
|
|
138
|
+
maxCpuMs: z.number(),
|
|
139
|
+
totalHeapDeltaBytes: z.number(),
|
|
140
|
+
positiveHeapDeltaBytes: z.number(),
|
|
141
|
+
maxHeapDeltaBytes: z.number(),
|
|
142
|
+
totalRssDeltaBytes: z.number(),
|
|
143
|
+
positiveRssDeltaBytes: z.number(),
|
|
144
|
+
maxRssDeltaBytes: z.number(),
|
|
145
|
+
firstSeenAt: z.string(),
|
|
146
|
+
lastSeenAt: z.string(),
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
const moduleResourceUsageSurfaceSummarySchema = z.object({
|
|
150
|
+
surface: moduleResourceSurfaceSchema,
|
|
151
|
+
calls: z.number().int(),
|
|
152
|
+
errors: z.number().int(),
|
|
153
|
+
totalDurationMs: z.number(),
|
|
154
|
+
p95DurationMs: z.number(),
|
|
155
|
+
totalCpuMs: z.number(),
|
|
156
|
+
positiveHeapDeltaBytes: z.number(),
|
|
157
|
+
positiveRssDeltaBytes: z.number(),
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
const moduleResourceUsageModuleSummarySchema = z.object({
|
|
161
|
+
moduleId: z.string(),
|
|
162
|
+
calls: z.number().int(),
|
|
163
|
+
errors: z.number().int(),
|
|
164
|
+
totalDurationMs: z.number(),
|
|
165
|
+
p95DurationMs: z.number(),
|
|
166
|
+
totalCpuMs: z.number(),
|
|
167
|
+
positiveHeapDeltaBytes: z.number(),
|
|
168
|
+
positiveRssDeltaBytes: z.number(),
|
|
169
|
+
surfaces: z.array(moduleResourceUsageSurfaceSummarySchema),
|
|
170
|
+
topOperations: z.array(moduleResourceUsageEntrySchema),
|
|
171
|
+
candidateReasons: z.array(z.string()),
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
const moduleResourceUsageBucketModuleSchema = z.object({
|
|
175
|
+
moduleId: z.string(),
|
|
176
|
+
calls: z.number().int(),
|
|
177
|
+
errors: z.number().int(),
|
|
178
|
+
totalDurationMs: z.number(),
|
|
179
|
+
p95DurationMs: z.number(),
|
|
180
|
+
totalCpuMs: z.number(),
|
|
181
|
+
positiveHeapDeltaBytes: z.number(),
|
|
182
|
+
positiveRssDeltaBytes: z.number(),
|
|
183
|
+
surfaces: z.array(moduleResourceUsageSurfaceSummarySchema),
|
|
184
|
+
topOperations: z.array(moduleResourceUsageEntrySchema),
|
|
185
|
+
candidateReasons: z.array(z.string()),
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
const moduleResourceUsageBucketSchema = z.object({
|
|
189
|
+
bucketStart: z.string().describe('UTC bucket start timestamp (ISO-8601)'),
|
|
190
|
+
bucketEnd: z.string().describe('UTC bucket end timestamp (ISO-8601)'),
|
|
191
|
+
bucketIntervalMs: z.number().int().describe('Bucket size used for this specific bucket in milliseconds'),
|
|
192
|
+
stage: z.enum(['startup', 'running']).describe('Lifecycle stage for this telemetry bucket'),
|
|
193
|
+
partial: z.boolean().describe('True when the bucket does not represent a complete process-observed interval'),
|
|
194
|
+
totals: z.object({
|
|
195
|
+
modules: z.number().int(),
|
|
196
|
+
calls: z.number().int(),
|
|
197
|
+
errors: z.number().int(),
|
|
198
|
+
totalDurationMs: z.number(),
|
|
199
|
+
totalCpuMs: z.number(),
|
|
200
|
+
positiveHeapDeltaBytes: z.number(),
|
|
201
|
+
positiveRssDeltaBytes: z.number(),
|
|
202
|
+
}),
|
|
203
|
+
modules: z.array(moduleResourceUsageBucketModuleSchema),
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
const moduleTelemetryReportSchema = z.object({
|
|
207
|
+
generatedAt: z.string().describe('Report generation timestamp (ISO-8601)'),
|
|
208
|
+
startedAt: z.string().describe('Telemetry aggregation start timestamp (ISO-8601)'),
|
|
209
|
+
enabled: z.boolean().describe('Whether module resource tracking is enabled'),
|
|
210
|
+
bucketIntervalMs: z.number().int().describe('Bucket size in milliseconds; module telemetry uses 5-minute buckets in every environment'),
|
|
211
|
+
totals: z.object({
|
|
212
|
+
modules: z.number().int(),
|
|
213
|
+
operations: z.number().int(),
|
|
214
|
+
calls: z.number().int(),
|
|
215
|
+
errors: z.number().int(),
|
|
216
|
+
totalDurationMs: z.number(),
|
|
217
|
+
totalCpuMs: z.number(),
|
|
218
|
+
positiveHeapDeltaBytes: z.number(),
|
|
219
|
+
positiveRssDeltaBytes: z.number(),
|
|
220
|
+
}),
|
|
221
|
+
thresholds: z.object({
|
|
222
|
+
p95DurationMs: z.number(),
|
|
223
|
+
cpuMs: z.number(),
|
|
224
|
+
positiveHeapDeltaBytes: z.number(),
|
|
225
|
+
positiveRssDeltaBytes: z.number(),
|
|
226
|
+
errors: z.number(),
|
|
227
|
+
}),
|
|
228
|
+
modules: z.array(moduleResourceUsageModuleSummarySchema),
|
|
229
|
+
candidates: z.array(moduleResourceUsageModuleSummarySchema),
|
|
230
|
+
buckets: z.array(moduleResourceUsageBucketSchema),
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
export const moduleTelemetryResponseSchema = moduleTelemetryReportSchema.extend({
|
|
234
|
+
canClearTelemetry: z.boolean().optional(),
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
export const moduleTelemetryClearResponseSchema = z.object({
|
|
238
|
+
cleared: z.boolean(),
|
|
239
|
+
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
const moduleTelemetryIcon = React.createElement(
|
|
4
|
+
'svg',
|
|
5
|
+
{
|
|
6
|
+
width: 16,
|
|
7
|
+
height: 16,
|
|
8
|
+
viewBox: '0 0 24 24',
|
|
9
|
+
fill: 'none',
|
|
10
|
+
stroke: 'currentColor',
|
|
11
|
+
strokeWidth: 2,
|
|
12
|
+
strokeLinecap: 'round',
|
|
13
|
+
strokeLinejoin: 'round',
|
|
14
|
+
},
|
|
15
|
+
React.createElement('path', { d: 'M4 19V5' }),
|
|
16
|
+
React.createElement('path', { d: 'M4 19h16' }),
|
|
17
|
+
React.createElement('path', { d: 'M7 15l3-3 3 2 5-7' }),
|
|
18
|
+
React.createElement('path', { d: 'M18 7h2v2' }),
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
export const metadata = {
|
|
22
|
+
requireAuth: true,
|
|
23
|
+
requireFeatures: ['configs.system_status.view'],
|
|
24
|
+
pageTitle: 'Module telemetry',
|
|
25
|
+
pageTitleKey: 'configs.config.nav.moduleTelemetry',
|
|
26
|
+
pageGroup: 'System',
|
|
27
|
+
pageGroupKey: 'settings.sections.system',
|
|
28
|
+
pageOrder: 2,
|
|
29
|
+
icon: moduleTelemetryIcon,
|
|
30
|
+
pageContext: 'settings' as const,
|
|
31
|
+
breadcrumb: [
|
|
32
|
+
{ label: 'Module telemetry', labelKey: 'configs.config.nav.moduleTelemetry' },
|
|
33
|
+
],
|
|
34
|
+
} as const
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
2
|
+
import ModuleTelemetryPanel from '../../../components/ModuleTelemetryPanel'
|
|
3
|
+
|
|
4
|
+
export default function ModuleTelemetryPage() {
|
|
5
|
+
return (
|
|
6
|
+
<Page>
|
|
7
|
+
<PageBody>
|
|
8
|
+
<ModuleTelemetryPanel />
|
|
9
|
+
</PageBody>
|
|
10
|
+
</Page>
|
|
11
|
+
)
|
|
12
|
+
}
|