@open-mercato/core 0.10.1-develop.7926.1.673c22ad09 → 0.10.1-develop.7932.1.ead5057eae
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/dist/modules/auth/commands/roles.js +20 -8
- package/dist/modules/auth/commands/roles.js.map +2 -2
- package/dist/modules/query_index/lib/indexer.js +39 -1
- package/dist/modules/query_index/lib/indexer.js.map +2 -2
- package/dist/modules/warranty_claims/backend/components/WarrantyWorkspace.js +1 -1
- package/dist/modules/warranty_claims/backend/components/WarrantyWorkspace.js.map +1 -1
- package/package.json +7 -7
- package/src/modules/auth/commands/roles.ts +20 -8
- package/src/modules/auth/i18n/de.json +1 -0
- package/src/modules/auth/i18n/en.json +1 -0
- package/src/modules/auth/i18n/es.json +1 -0
- package/src/modules/auth/i18n/ko.json +1 -0
- package/src/modules/auth/i18n/pl.json +1 -0
- package/src/modules/business_rules/i18n/de.json +1 -1
- package/src/modules/business_rules/i18n/en.json +1 -1
- package/src/modules/business_rules/i18n/es.json +1 -1
- package/src/modules/business_rules/i18n/ko.json +1 -1
- package/src/modules/business_rules/i18n/pl.json +1 -1
- package/src/modules/query_index/lib/indexer.ts +44 -1
- package/src/modules/warranty_claims/backend/components/WarrantyWorkspace.tsx +1 -1
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
buildChanges,
|
|
8
8
|
requireId
|
|
9
9
|
} from "@open-mercato/shared/lib/commands/helpers";
|
|
10
|
-
import { CrudHttpError, forbidden } from "@open-mercato/shared/lib/crud/errors";
|
|
10
|
+
import { CrudHttpError, forbidden, isUniqueViolation } from "@open-mercato/shared/lib/crud/errors";
|
|
11
11
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
12
12
|
import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
13
13
|
import { z } from "zod";
|
|
@@ -137,13 +137,25 @@ const createRoleCommand = {
|
|
|
137
137
|
throw new CrudHttpError(400, { error: "tenantId is required \u2014 global roles are not supported" });
|
|
138
138
|
}
|
|
139
139
|
const de = ctx.container.resolve("dataEngine");
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
140
|
+
let role;
|
|
141
|
+
try {
|
|
142
|
+
role = await de.createOrmEntity({
|
|
143
|
+
entity: Role,
|
|
144
|
+
data: {
|
|
145
|
+
name: parsed.name,
|
|
146
|
+
tenantId: resolvedTenantId
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
} catch (error) {
|
|
150
|
+
if (!isUniqueViolation(error, "roles_tenant_id_name_unique")) throw error;
|
|
151
|
+
const { translate } = await resolveTranslations();
|
|
152
|
+
const message = translate("auth.roles.errors.nameExists", "A role with this name already exists");
|
|
153
|
+
throw new CrudHttpError(400, {
|
|
154
|
+
error: message,
|
|
155
|
+
fieldErrors: { name: message },
|
|
156
|
+
details: [{ path: ["name"], message, code: "duplicate", origin: "validation" }]
|
|
157
|
+
});
|
|
158
|
+
}
|
|
147
159
|
await setCustomFieldsIfAny({
|
|
148
160
|
dataEngine: de,
|
|
149
161
|
entityId: E.auth.role,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/auth/commands/roles.ts"],
|
|
4
|
-
"sourcesContent": ["import type { CommandHandler, CommandReplayAuthorizationArgs, CommandUndoLogEntry } from '@open-mercato/shared/lib/commands'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport {\n parseWithCustomFields,\n setCustomFieldsIfAny,\n emitCrudSideEffects,\n emitCrudUndoSideEffects,\n buildChanges,\n requireId,\n} from '@open-mercato/shared/lib/commands/helpers'\nimport type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport { CrudHttpError, forbidden } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { z } from 'zod'\nimport { Role, RoleAcl, UserRole } from '@open-mercato/core/modules/auth/data/entities'\nimport { E } from '#generated/entities.ids.generated'\nimport {\n loadCustomFieldSnapshot,\n buildCustomFieldResetMap,\n diffCustomFieldChanges,\n} from '@open-mercato/shared/lib/commands/customFieldSnapshots'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { resolveRedoSnapshot } from '@open-mercato/shared/lib/commands/redo'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { resolveIsSuperAdmin, normalizeTenantId } from '@open-mercato/core/modules/auth/lib/tenantAccess'\nimport type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport {\n assertActorCanAccessRoleTarget,\n assertActorCanGrantAcl,\n assertActorCanModifySuperAdminRoleTarget,\n} from '@open-mercato/core/modules/auth/lib/grantChecks'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport {\n assertReplaySnapshotMatches,\n extractStoredReplayInput,\n lockReplayAuthorizationState,\n rerunReplayTransactionGuardAfterLocks,\n requireCurrentReplayFeature,\n requireCurrentReplaySuperAdmin,\n requireTransactionalReplayEntityManager,\n} from '@open-mercato/core/modules/auth/lib/commandReplay'\nimport { lockRoleWriterAuthorizationState } from '@open-mercato/core/modules/auth/lib/authorizationStateLocks'\n\ntype SerializedRole = {\n name: string\n tenantId: string\n custom?: Record<string, unknown>\n}\n\ntype RoleAclSnapshot = {\n id: string | null\n tenantId: string\n features: string[] | null\n isSuperAdmin: boolean\n organizations: string[] | null\n}\n\ntype RoleUndoSnapshot = {\n id: string\n name: string\n tenantId: string\n acls: RoleAclSnapshot[]\n custom?: Record<string, unknown>\n}\n\ntype RoleSnapshots = {\n view: SerializedRole\n undo: RoleUndoSnapshot\n}\n\nconst ROLE_REDO_LOG = Symbol('auth.roles.redoLog')\n\ntype RoleReplayRuntimeContext = CommandRuntimeContext & {\n [ROLE_REDO_LOG]?: CommandUndoLogEntry\n}\n\nfunction withRoleRedoLog(ctx: CommandRuntimeContext, logEntry: CommandUndoLogEntry): RoleReplayRuntimeContext {\n return { ...ctx, [ROLE_REDO_LOG]: logEntry }\n}\n\nfunction resolveCommandEm(ctx: CommandRuntimeContext, forkWhenUnbound = false): EntityManager {\n if (ctx.transactionalEm) return ctx.transactionalEm\n const em = ctx.container.resolve('em') as EntityManager\n return forkWhenUnbound ? em.fork() : em\n}\n\nfunction resolveActorTenantScope(ctx: CommandRuntimeContext): string | null {\n if (ctx.systemActor === true) return null\n const auth = ctx.auth\n if (!auth) return null\n if ((auth as { isSuperAdmin?: boolean }).isSuperAdmin === true) return null\n return normalizeTenantId(auth.tenantId ?? null) ?? null\n}\n\nfunction assertRoleTenantInScope(actorTenantScope: string | null, targetTenantId: unknown): void {\n if (!actorTenantScope) return\n const targetTenant = normalizeTenantId(targetTenantId) ?? null\n if (!targetTenant || targetTenant !== actorTenantScope) {\n throw new CrudHttpError(404, { error: 'Role not found' })\n }\n}\n\nconst RESERVED_ROLE_NAMES = new Set(['superadmin', 'admin'])\n\nfunction isReservedRoleName(name: string | undefined | null): boolean {\n if (typeof name !== 'string') return false\n const normalized = name.trim().toLowerCase()\n return normalized.length > 0 && RESERVED_ROLE_NAMES.has(normalized)\n}\n\nfunction assertRoleNameAllowed(name: string | undefined | null) {\n if (isReservedRoleName(name)) {\n throw new CrudHttpError(400, { error: 'Role name is reserved' })\n }\n}\n\ntype ResolvedActorScope = { isSuperAdmin: boolean; actorTenantId: string | null }\n\nasync function resolveActorScope(ctx: { auth: { tenantId?: string | null; sub?: string | null; orgId?: string | null; isSuperAdmin?: boolean } | null; container: { resolve<T = unknown>(name: string): T } }): Promise<ResolvedActorScope> {\n const isSuperAdmin = await resolveIsSuperAdmin(ctx)\n const actorTenantId = normalizeTenantId(ctx.auth?.tenantId ?? null) ?? null\n return { isSuperAdmin, actorTenantId }\n}\n\nfunction buildScopedRoleFilter(roleId: string, scope: ResolvedActorScope): FilterQuery<Role> {\n const filter: FilterQuery<Role> = { id: roleId, deletedAt: null }\n if (!scope.isSuperAdmin) {\n ;(filter as Record<string, unknown>).tenantId = scope.actorTenantId\n }\n return filter\n}\n\nconst createSchema = z.object({\n name: z.string().min(2).max(100),\n tenantId: z.string().uuid().optional(),\n})\n\nconst updateSchema = z.object({\n id: z.string().uuid(),\n name: z.string().min(2).max(100).optional(),\n tenantId: z.string().uuid().optional(),\n})\n\nexport const roleCrudEvents: CrudEventsConfig = {\n module: 'auth',\n entity: 'role',\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\nexport const roleCrudIndexer: CrudIndexerConfig = {\n entityType: E.auth.role,\n buildUpsertPayload: (ctx) => ({\n entityType: E.auth.role,\n recordId: ctx.identifiers.id,\n tenantId: ctx.identifiers.tenantId,\n }),\n buildDeletePayload: (ctx) => ({\n entityType: E.auth.role,\n recordId: ctx.identifiers.id,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\nconst createRoleCommand: CommandHandler<Record<string, unknown>, Role> = {\n id: 'auth.roles.create',\n atomicReplay: true,\n stabilizeReplay: stabilizeRoleReplay,\n authorizeReplay: (params) => authorizeRoleReplay('create', params),\n async execute(rawInput, ctx) {\n const rawBody = rawInput && typeof rawInput === 'object' ? rawInput as Record<string, unknown> : {}\n if ('tenantId' in rawBody && rawBody.tenantId === null) {\n throw new CrudHttpError(400, { error: 'tenantId cannot be null \u2014 global roles are not supported' })\n }\n const { parsed, custom } = parseWithCustomFields(createSchema, rawInput)\n assertRoleNameAllowed(parsed.name)\n const scope = await resolveActorScope(ctx)\n const requestedTenantId = parsed.tenantId ?? null\n if (!scope.isSuperAdmin && requestedTenantId && requestedTenantId !== scope.actorTenantId) {\n throw new CrudHttpError(403, { error: 'Not authorized to target this tenant.' })\n }\n const resolvedTenantId = scope.isSuperAdmin\n ? (requestedTenantId ?? scope.actorTenantId)\n : scope.actorTenantId\n if (!resolvedTenantId) {\n throw new CrudHttpError(400, { error: 'tenantId is required \u2014 global roles are not supported' })\n }\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n const role = await de.createOrmEntity({\n entity: Role,\n data: {\n name: parsed.name,\n tenantId: resolvedTenantId,\n },\n })\n\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.role,\n recordId: String(role.id),\n organizationId: null,\n tenantId: resolvedTenantId,\n values: custom,\n })\n\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'created',\n entity: role,\n identifiers: {\n id: String(role.id),\n organizationId: null,\n tenantId: resolvedTenantId,\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n })\n\n return role\n },\n captureAfter: async (_input, result, ctx) => {\n const em = resolveCommandEm(ctx, true)\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n })\n return serializeRole(result, custom)\n },\n buildLog: async ({ result, ctx }) => {\n const { translate } = await resolveTranslations()\n const em = resolveCommandEm(ctx, true)\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n })\n const snapshot = captureRoleSnapshots(result, [], custom)\n return {\n actionLabel: translate('auth.audit.roles.create', 'Create role'),\n resourceKind: 'auth.role',\n resourceId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n snapshotAfter: snapshot.view,\n payload: {\n undo: {\n after: snapshot.undo,\n },\n },\n }\n },\n undo: async ({ input, logEntry, ctx }) => {\n const undo = extractUndoPayload<RoleUndoPayload>(logEntry)?.after\n if (!undo) return\n const em = resolveCommandEm(ctx)\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await withAtomicFlush(em, [async () => {\n await authorizeRoleReplayAtMutation('create', {\n operation: 'undo',\n input,\n ctx,\n logEntry,\n })\n await lockRoleWriterAuthorizationState(em, [undo.id])\n await em.nativeDelete(RoleAcl, { role: undo.id as unknown as Role })\n if (undo.custom && Object.keys(undo.custom).length) {\n const reset = buildCustomFieldResetMap(undefined, undo.custom)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.role,\n recordId: undo.id,\n organizationId: null,\n tenantId: undo.tenantId ?? null,\n values: reset,\n notify: false,\n })\n }\n }\n await de.deleteOrmEntity({\n entity: Role,\n where: { id: undo.id, deletedAt: null } as FilterQuery<Role>,\n soft: false,\n })\n }], { transaction: true, label: 'auth.roles.create.undo' })\n },\n redo: async ({ input, logEntry, ctx }) => {\n const after = resolveRedoSnapshot<RoleUndoSnapshot>(logEntry)\n if (!after) throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for role create' })\n const em = resolveCommandEm(ctx)\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n let role: Role | null = null\n await withAtomicFlush(em, [async () => {\n await authorizeRoleReplayAtMutation('create', {\n operation: 'redo',\n input,\n ctx,\n logEntry,\n })\n role = await findOneWithDecryption(em, Role, { id: after.id }, {}, { tenantId: null, organizationId: null })\n if (role) {\n role.deletedAt = null\n role.name = after.name\n role.tenantId = after.tenantId\n await em.flush()\n } else {\n role = await de.createOrmEntity({\n entity: Role,\n data: {\n id: after.id,\n name: after.name,\n tenantId: after.tenantId,\n },\n })\n }\n await restoreRoleAcls(em, after.id, after.acls)\n if (after.custom && Object.keys(after.custom).length) {\n const reset = buildCustomFieldResetMap(after.custom, undefined)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.role,\n recordId: after.id,\n organizationId: null,\n tenantId: after.tenantId ?? null,\n values: reset,\n notify: false,\n })\n }\n }\n }], { transaction: true, label: 'auth.roles.create.redo' })\n if (!role) throw new CrudHttpError(400, { error: '[internal] redo failed to restore role row' })\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'created',\n entity: role,\n identifiers: {\n id: after.id,\n organizationId: null,\n tenantId: after.tenantId ?? null,\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n })\n return role\n },\n}\n\nconst updateRoleCommand: CommandHandler<Record<string, unknown>, Role> = {\n id: 'auth.roles.update',\n atomicReplay: true,\n stabilizeReplay: stabilizeRoleReplay,\n authorizeReplay: (params) => authorizeRoleReplay('update', params),\n async prepare(rawInput, ctx) {\n const { parsed } = parseWithCustomFields(updateSchema, rawInput)\n const em = resolveCommandEm(ctx)\n const scope = await resolveActorScope(ctx)\n const existing = await findOneWithDecryption(em, Role, buildScopedRoleFilter(parsed.id, scope), {}, { tenantId: scope.actorTenantId, organizationId: null })\n if (!existing) throw new CrudHttpError(404, { error: 'Role not found' })\n assertRoleTenantInScope(resolveActorTenantScope(ctx), existing.tenantId)\n const acls = await loadRoleAclSnapshots(em, parsed.id)\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: parsed.id,\n tenantId: existing.tenantId ? String(existing.tenantId) : null,\n })\n return { before: captureRoleSnapshots(existing, acls, custom) }\n },\n async execute(rawInput, ctx) {\n const { parsed, custom } = parseWithCustomFields(updateSchema, rawInput)\n const em = resolveCommandEm(ctx)\n const scope = await resolveActorScope(ctx)\n const current = await findOneWithDecryption(em, Role, buildScopedRoleFilter(parsed.id, scope), {}, { tenantId: scope.actorTenantId, organizationId: null })\n if (!current) throw new CrudHttpError(404, { error: 'Role not found' })\n const actorTenantScope = resolveActorTenantScope(ctx)\n assertRoleTenantInScope(actorTenantScope, current.tenantId)\n if (parsed.name !== undefined) {\n const nextName = parsed.name\n if (nextName !== current.name) assertRoleNameAllowed(nextName)\n if (nextName !== current.name) {\n const assignments = await em.count(UserRole, { role: current, deletedAt: null })\n if (assignments > 0) {\n throw new CrudHttpError(400, { error: 'Role name cannot be changed while users are assigned' })\n }\n }\n }\n const wantsTenantChange = parsed.tenantId !== undefined && parsed.tenantId !== current.tenantId\n if (wantsTenantChange) {\n if (!scope.isSuperAdmin) {\n throw new CrudHttpError(403, { error: 'Not authorized to target this tenant.' })\n }\n const assignments = await em.count(UserRole, { role: current, deletedAt: null })\n if (assignments > 0) {\n throw new CrudHttpError(400, { error: 'Role cannot be moved to another tenant while users are assigned' })\n }\n }\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n const redoLogEntry = (ctx as RoleReplayRuntimeContext)[ROLE_REDO_LOG]\n let role: Role | null = null\n await withAtomicFlush(em, [async () => {\n if (redoLogEntry) {\n await authorizeRoleReplayAtMutation('update', {\n operation: 'redo',\n input: rawInput,\n ctx,\n logEntry: redoLogEntry,\n })\n }\n await lockRoleWriterAuthorizationState(em, [parsed.id])\n if (wantsTenantChange) {\n await em.nativeDelete(RoleAcl, { role: parsed.id as unknown as Role })\n }\n role = await de.updateOrmEntity({\n entity: Role,\n where: buildScopedRoleFilter(parsed.id, scope),\n apply: (entity) => {\n if (parsed.name !== undefined) entity.name = parsed.name\n if (parsed.tenantId !== undefined && scope.isSuperAdmin) entity.tenantId = parsed.tenantId\n },\n })\n if (!role) throw new CrudHttpError(404, { error: 'Role not found' })\n\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.role,\n recordId: String(role.id),\n organizationId: null,\n tenantId: role.tenantId ? String(role.tenantId) : null,\n values: custom,\n })\n }], {\n transaction: true,\n label: redoLogEntry ? 'auth.roles.update.redo' : 'auth.roles.update',\n })\n const updatedRole = role as Role | null\n if (!updatedRole) throw new CrudHttpError(404, { error: 'Role not found' })\n\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: updatedRole,\n identifiers: {\n id: String(updatedRole.id),\n organizationId: null,\n tenantId: updatedRole.tenantId ? String(updatedRole.tenantId) : null,\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n })\n\n return updatedRole\n },\n captureAfter: async (_input, result, ctx) => {\n const em = resolveCommandEm(ctx, true)\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n })\n return serializeRole(result, custom)\n },\n buildLog: async ({ result, snapshots, ctx }) => {\n const { translate } = await resolveTranslations()\n const beforeSnapshots = snapshots.before as RoleSnapshots | undefined\n const before = beforeSnapshots?.view\n const beforeUndo = beforeSnapshots?.undo ?? null\n const em = resolveCommandEm(ctx, true)\n const afterAcls = await loadRoleAclSnapshots(em, String(result.id))\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n })\n const afterSnapshots = captureRoleSnapshots(result, afterAcls, custom)\n const after = afterSnapshots.view\n const changes = buildChanges(before ?? null, after as Record<string, unknown>, ['name', 'tenantId'])\n const customDiff = diffCustomFieldChanges(before?.custom, custom)\n for (const [key, diff] of Object.entries(customDiff)) {\n changes[`cf_${key}`] = diff\n }\n return {\n actionLabel: translate('auth.audit.roles.update', 'Update role'),\n resourceKind: 'auth.role',\n resourceId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n changes,\n snapshotBefore: before ?? null,\n snapshotAfter: after,\n payload: {\n undo: {\n before: beforeUndo,\n after: afterSnapshots.undo,\n },\n },\n }\n },\n undo: async ({ input, logEntry, ctx }) => {\n const undo = extractUndoPayload<RoleUndoPayload>(logEntry)\n const before = undo?.before\n const after = undo?.after\n if (!before) return\n const storedReplayInput = extractStoredReplayInput(logEntry.commandPayload, logEntry)\n const replayInput = Object.keys(storedReplayInput).length\n ? storedReplayInput\n : deriveRoleUpdateReplayInput(before, after ?? null)\n const em = resolveCommandEm(ctx)\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n let updated: Role | null = null\n await withAtomicFlush(em, [async () => {\n await authorizeRoleReplayAtMutation('update', {\n operation: 'undo',\n input,\n ctx,\n logEntry,\n })\n updated = await de.updateOrmEntity({\n entity: Role,\n where: { id: before.id, deletedAt: null } as FilterQuery<Role>,\n apply: (entity) => {\n if ('name' in replayInput) entity.name = before.name\n if ('tenantId' in replayInput) entity.tenantId = before.tenantId\n },\n })\n const restoresAcls = 'tenantId' in replayInput && after?.tenantId !== before.tenantId\n if (updated && restoresAcls) {\n await restoreRoleAcls(em, before.id, before.acls)\n }\n const reset = buildCustomFieldResetMap(before.custom, after?.custom)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.role,\n recordId: before.id,\n organizationId: null,\n tenantId: before.tenantId,\n values: reset,\n notify: false,\n })\n }\n }], { transaction: true, label: 'auth.roles.update.undo' })\n await emitCrudUndoSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: updated,\n identifiers: {\n id: before.id,\n organizationId: null,\n tenantId: before.tenantId ?? null,\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n })\n },\n redo: ({ input, ctx, logEntry }) => updateRoleCommand.execute(input, withRoleRedoLog(ctx, logEntry)),\n}\n\nconst deleteRoleCommand: CommandHandler<{ body?: Record<string, unknown>; query?: Record<string, unknown> }, Role> = {\n id: 'auth.roles.delete',\n atomicReplay: true,\n stabilizeReplay: stabilizeRoleReplay,\n authorizeReplay: (params) => authorizeRoleReplay('delete', params),\n async prepare(input, ctx) {\n const id = requireId(input, 'Role id required')\n const em = resolveCommandEm(ctx)\n const scope = await resolveActorScope(ctx)\n const existing = await findOneWithDecryption(em, Role, buildScopedRoleFilter(id, scope), {}, { tenantId: scope.actorTenantId, organizationId: null })\n if (!existing) return {}\n const actorTenantScope = resolveActorTenantScope(ctx)\n if (actorTenantScope) {\n const targetTenant = normalizeTenantId(existing.tenantId) ?? null\n if (!targetTenant || targetTenant !== actorTenantScope) return {}\n }\n const acls = await loadRoleAclSnapshots(em, id)\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: id,\n tenantId: existing.tenantId ? String(existing.tenantId) : null,\n })\n return { before: captureRoleSnapshots(existing, acls, custom) }\n },\n async execute(input, ctx) {\n const id = requireId(input, 'Role id required')\n const em = resolveCommandEm(ctx)\n const scope = await resolveActorScope(ctx)\n const role = await findOneWithDecryption(em, Role, buildScopedRoleFilter(id, scope), {}, { tenantId: scope.actorTenantId, organizationId: null })\n if (!role) throw new CrudHttpError(404, { error: 'Role not found' })\n assertRoleTenantInScope(resolveActorTenantScope(ctx), role.tenantId)\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n const redoLogEntry = (ctx as RoleReplayRuntimeContext)[ROLE_REDO_LOG]\n let deleted: Role | null = null\n await withAtomicFlush(em, [async () => {\n if (redoLogEntry) {\n await authorizeRoleReplayAtMutation('delete', {\n operation: 'redo',\n input,\n ctx,\n logEntry: redoLogEntry,\n })\n }\n await lockRoleWriterAuthorizationState(em, [id])\n const activeAssignments = await em.count(UserRole, { role, deletedAt: null })\n if (activeAssignments > 0) throw new CrudHttpError(400, { error: 'Role has assigned users' })\n await em.nativeDelete(RoleAcl, { role: id })\n deleted = await de.deleteOrmEntity({\n entity: Role,\n where: buildScopedRoleFilter(id, scope),\n soft: false,\n })\n if (!deleted) throw new CrudHttpError(404, { error: 'Role not found' })\n }], {\n transaction: true,\n label: redoLogEntry ? 'auth.roles.delete.redo' : 'auth.roles.delete',\n })\n const deletedRole = deleted as Role | null\n if (!deletedRole) throw new CrudHttpError(404, { error: 'Role not found' })\n\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'deleted',\n entity: deletedRole,\n identifiers: {\n id,\n organizationId: null,\n tenantId: deletedRole.tenantId ? String(deletedRole.tenantId) : null,\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n })\n\n return deletedRole\n },\n buildLog: async ({ snapshots, input }) => {\n const { translate } = await resolveTranslations()\n const beforeSnapshots = snapshots.before as RoleSnapshots | undefined\n const before = beforeSnapshots?.view\n const beforeUndo = beforeSnapshots?.undo ?? null\n const id = requireId(input, 'Role id required')\n return {\n actionLabel: translate('auth.audit.roles.delete', 'Delete role'),\n resourceKind: 'auth.role',\n resourceId: id,\n tenantId: before?.tenantId ?? null,\n snapshotBefore: before ?? null,\n payload: {\n undo: {\n before: beforeUndo,\n },\n },\n }\n },\n undo: async ({ input, logEntry, ctx }) => {\n const before = extractUndoPayload<RoleUndoPayload>(logEntry)?.before\n if (!before) return\n const em = resolveCommandEm(ctx)\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n let role: Role | null = null\n await withAtomicFlush(em, [async () => {\n await authorizeRoleReplayAtMutation('delete', {\n operation: 'undo',\n input,\n ctx,\n logEntry,\n })\n role = await findOneWithDecryption(em, Role, { id: before.id }, {}, { tenantId: null, organizationId: null })\n if (role) {\n role.deletedAt = null\n role.name = before.name\n role.tenantId = before.tenantId\n await em.flush()\n } else {\n role = await de.createOrmEntity({\n entity: Role,\n data: {\n id: before.id,\n name: before.name,\n tenantId: before.tenantId,\n },\n })\n }\n await restoreRoleAcls(em, before.id, before.acls)\n const reset = buildCustomFieldResetMap(before.custom, undefined)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.role,\n recordId: before.id,\n organizationId: null,\n tenantId: before.tenantId ?? null,\n values: reset,\n notify: false,\n })\n }\n }], { transaction: true, label: 'auth.roles.delete.undo' })\n if (!role) throw new CrudHttpError(400, { error: '[internal] undo failed to restore role row' })\n await emitCrudUndoSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: role,\n identifiers: {\n id: before.id,\n organizationId: null,\n tenantId: before.tenantId ?? null,\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n })\n },\n redo: ({ input, ctx, logEntry }) => deleteRoleCommand.execute(input, withRoleRedoLog(ctx, logEntry)),\n}\n\ntype RoleReplayCommandKind = 'create' | 'update' | 'delete'\n\nfunction normalizeRoleAclSnapshots(acls: RoleAclSnapshot[]): RoleAclSnapshot[] {\n return acls\n .map((acl) => ({\n ...acl,\n features: acl.features ? [...acl.features].sort((left, right) => left.localeCompare(right)) : null,\n organizations: acl.organizations ? [...acl.organizations].sort((left, right) => left.localeCompare(right)) : null,\n }))\n .sort((left, right) => {\n const tenantOrder = left.tenantId.localeCompare(right.tenantId)\n if (tenantOrder !== 0) return tenantOrder\n return (left.id ?? '').localeCompare(right.id ?? '')\n })\n}\n\nfunction normalizeRoleReplaySnapshot(snapshot: RoleUndoSnapshot): RoleUndoSnapshot {\n return {\n ...snapshot,\n acls: normalizeRoleAclSnapshots(snapshot.acls),\n }\n}\n\nfunction toRoleUpdateReplayState(snapshot: RoleUndoSnapshot): SerializedRole {\n return {\n name: snapshot.name,\n tenantId: snapshot.tenantId,\n ...(snapshot.custom && Object.keys(snapshot.custom).length ? { custom: snapshot.custom } : {}),\n }\n}\n\nfunction deriveRoleUpdateReplayInput(\n before: RoleUndoSnapshot | null,\n after: RoleUndoSnapshot | null,\n): Record<string, unknown> {\n if (!before) return {}\n if (!after) return { id: before.id, name: before.name, tenantId: before.tenantId }\n const input: Record<string, unknown> = { id: after.id }\n if (before.name !== after.name) input.name = after.name\n if (before.tenantId !== after.tenantId) input.tenantId = after.tenantId\n return input\n}\n\nasync function authorizeRoleReplay(\n commandKind: RoleReplayCommandKind,\n params: CommandReplayAuthorizationArgs<unknown>,\n): Promise<void> {\n const { operation, ctx, logEntry } = params\n const em = requireTransactionalReplayEntityManager(ctx)\n await requireCurrentReplayFeature(ctx, 'auth.roles.manage')\n\n const undoPayload = extractUndoPayload<RoleUndoPayload>(logEntry)\n const before = undoPayload?.before ?? null\n const after = undoPayload?.after ?? null\n const roleId = logEntry.resourceId ?? before?.id ?? after?.id ?? null\n if (!roleId) {\n await assertReplaySnapshotMatches(null, { required: 'role replay snapshot' })\n return\n }\n\n const currentRole = await findOneWithDecryption(\n em,\n Role,\n { id: roleId, deletedAt: null },\n {},\n { tenantId: null, organizationId: null },\n )\n const rbacService = ctx.systemActor === true\n ? null\n : ctx.container.resolve('rbacService') as RbacService\n if (rbacService && currentRole) {\n await assertActorCanAccessRoleTarget({\n em,\n rbacService,\n requireBoundRbac: true,\n actorUserId: ctx.auth?.sub,\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.auth?.orgId ?? null,\n targetRoleId: roleId,\n })\n await assertActorCanModifySuperAdminRoleTarget({\n em,\n rbacService,\n requireBoundRbac: true,\n actorUserId: ctx.auth?.sub,\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.auth?.orgId ?? null,\n targetRoleId: roleId,\n })\n }\n\n let currentSnapshot: RoleUndoSnapshot | null = null\n if (currentRole) {\n const currentTenantId = currentRole.tenantId ? String(currentRole.tenantId) : null\n const [acls, custom] = await Promise.all([\n loadRoleAclSnapshots(em, roleId),\n loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: roleId,\n tenantId: currentTenantId,\n }),\n ])\n currentSnapshot = captureRoleSnapshots(currentRole, acls, custom).undo\n }\n\n const expectedCurrent = operation === 'undo'\n ? commandKind === 'create' ? after : commandKind === 'update' ? after : null\n : commandKind === 'create' ? null : before\n const storedReplayInput = extractStoredReplayInput(params.input, logEntry)\n const replayInput = Object.keys(storedReplayInput).length\n ? storedReplayInput\n : deriveRoleUpdateReplayInput(before, after)\n const movesTenant = commandKind === 'update'\n && 'tenantId' in replayInput\n && before?.tenantId !== after?.tenantId\n if (movesTenant) {\n await requireCurrentReplaySuperAdmin(ctx)\n }\n\n if (commandKind === 'update' && !movesTenant) {\n if (!currentSnapshot || !expectedCurrent) {\n await assertReplaySnapshotMatches(currentSnapshot, expectedCurrent)\n } else {\n await assertReplaySnapshotMatches(\n toRoleUpdateReplayState(currentSnapshot),\n toRoleUpdateReplayState(expectedCurrent),\n )\n }\n } else {\n await assertReplaySnapshotMatches(\n currentSnapshot ? normalizeRoleReplaySnapshot(currentSnapshot) : null,\n expectedCurrent ? normalizeRoleReplaySnapshot(expectedCurrent) : null,\n )\n }\n\n const desiredState = operation === 'undo'\n ? commandKind === 'create' ? null : before\n : commandKind === 'delete' ? null : after\n if (desiredState && !currentRole && rbacService) {\n const actorUserId = ctx.auth?.sub\n if (!actorUserId) throw forbidden()\n const actorAcl = await rbacService.loadAclWithEntityManager(em, actorUserId, {\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.auth?.orgId ?? null,\n })\n if (!actorAcl.isSuperAdmin) assertRoleTenantInScope(ctx.auth?.tenantId ?? null, desiredState.tenantId)\n }\n const restoresAcls = Boolean(\n desiredState\n && desiredState.acls.length > 0\n && (commandKind !== 'update' || movesTenant),\n )\n if (!restoresAcls || !desiredState || !rbacService) return\n\n await requireCurrentReplayFeature(ctx, 'auth.acl.manage')\n for (const acl of desiredState.acls) {\n await assertActorCanGrantAcl({\n em,\n rbacService,\n requireBoundRbac: true,\n actorUserId: ctx.auth?.sub,\n tenantId: acl.tenantId,\n organizationId: null,\n features: acl.features,\n isSuperAdmin: acl.isSuperAdmin,\n organizations: acl.organizations,\n })\n }\n}\n\nasync function authorizeRoleReplayAtMutation(\n commandKind: RoleReplayCommandKind,\n params: CommandReplayAuthorizationArgs<unknown>,\n): Promise<void> {\n const em = params.ctx.transactionalEm\n ?? params.ctx.container.resolve('em') as EntityManager\n const boundParams = params.ctx.transactionalEm\n ? params\n : { ...params, ctx: { ...params.ctx, transactionalEm: em } }\n await stabilizeRoleReplay(boundParams)\n await rerunReplayTransactionGuardAfterLocks(boundParams, em)\n await authorizeRoleReplay(commandKind, boundParams)\n}\n\nasync function stabilizeRoleReplay(\n params: CommandReplayAuthorizationArgs<unknown>,\n): Promise<void> {\n const undoPayload = extractUndoPayload<RoleUndoPayload>(params.logEntry)\n const before = undoPayload?.before ?? null\n const after = undoPayload?.after ?? null\n const targetRoleId = params.logEntry.resourceId ?? before?.id ?? after?.id ?? null\n const em = requireTransactionalReplayEntityManager(params.ctx)\n await lockReplayAuthorizationState(em, params.ctx, {\n targetRoleIds: targetRoleId ? [targetRoleId] : [],\n targetTenantIds: [\n ...(before?.tenantId ? [before.tenantId] : []),\n ...(after?.tenantId ? [after.tenantId] : []),\n ...(params.logEntry.tenantId ? [params.logEntry.tenantId] : []),\n ],\n })\n}\n\nregisterCommand(createRoleCommand)\nregisterCommand(updateRoleCommand)\nregisterCommand(deleteRoleCommand)\n\nfunction serializeRole(role: Role, custom?: Record<string, unknown> | null): SerializedRole {\n const payload: SerializedRole = {\n name: String(role.name ?? ''),\n tenantId: String(role.tenantId),\n }\n if (custom && Object.keys(custom).length) payload.custom = custom\n return payload\n}\n\nfunction captureRoleSnapshots(\n role: Role,\n acls: RoleAclSnapshot[] = [],\n custom?: Record<string, unknown> | null\n): RoleSnapshots {\n return {\n view: serializeRole(role, custom),\n undo: {\n id: String(role.id),\n name: String(role.name ?? ''),\n tenantId: String(role.tenantId),\n acls,\n ...(custom && Object.keys(custom).length ? { custom } : {}),\n },\n }\n}\n\nasync function loadRoleAclSnapshots(em: EntityManager, roleId: string): Promise<RoleAclSnapshot[]> {\n const entries = await findWithDecryption(em, RoleAcl, { role: roleId as unknown as Role, deletedAt: null }, {}, { tenantId: null, organizationId: null })\n return entries.map((entry) => ({\n id: entry.id ? String(entry.id) : null,\n tenantId: String(entry.tenantId),\n features: Array.isArray(entry.featuresJson) ? [...entry.featuresJson] : null,\n isSuperAdmin: Boolean(entry.isSuperAdmin),\n organizations: Array.isArray(entry.organizationsJson) ? [...entry.organizationsJson] : null,\n }))\n}\n\nasync function restoreRoleAcls(em: EntityManager, roleId: string, acls: RoleAclSnapshot[]) {\n await lockRoleWriterAuthorizationState(em, [roleId])\n await em.nativeDelete(RoleAcl, { role: roleId as unknown as Role })\n if (!acls.length) {\n await em.flush()\n return\n }\n const roleRef = em.getReference(Role, roleId)\n for (const acl of acls) {\n const entity = em.create(RoleAcl, {\n id: acl.id ?? undefined,\n role: roleRef,\n tenantId: acl.tenantId,\n featuresJson: acl.features ?? null,\n isSuperAdmin: acl.isSuperAdmin,\n organizationsJson: acl.organizations ?? null,\n createdAt: new Date(),\n })\n em.persist(entity)\n }\n await em.flush()\n}\n\ntype RoleUndoPayload = { before?: RoleUndoSnapshot | null; after?: RoleUndoSnapshot | null }\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import type { CommandHandler, CommandReplayAuthorizationArgs, CommandUndoLogEntry } from '@open-mercato/shared/lib/commands'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport {\n parseWithCustomFields,\n setCustomFieldsIfAny,\n emitCrudSideEffects,\n emitCrudUndoSideEffects,\n buildChanges,\n requireId,\n} from '@open-mercato/shared/lib/commands/helpers'\nimport type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'\nimport { CrudHttpError, forbidden, isUniqueViolation } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { z } from 'zod'\nimport { Role, RoleAcl, UserRole } from '@open-mercato/core/modules/auth/data/entities'\nimport { E } from '#generated/entities.ids.generated'\nimport {\n loadCustomFieldSnapshot,\n buildCustomFieldResetMap,\n diffCustomFieldChanges,\n} from '@open-mercato/shared/lib/commands/customFieldSnapshots'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { resolveRedoSnapshot } from '@open-mercato/shared/lib/commands/redo'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { resolveIsSuperAdmin, normalizeTenantId } from '@open-mercato/core/modules/auth/lib/tenantAccess'\nimport type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport {\n assertActorCanAccessRoleTarget,\n assertActorCanGrantAcl,\n assertActorCanModifySuperAdminRoleTarget,\n} from '@open-mercato/core/modules/auth/lib/grantChecks'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport {\n assertReplaySnapshotMatches,\n extractStoredReplayInput,\n lockReplayAuthorizationState,\n rerunReplayTransactionGuardAfterLocks,\n requireCurrentReplayFeature,\n requireCurrentReplaySuperAdmin,\n requireTransactionalReplayEntityManager,\n} from '@open-mercato/core/modules/auth/lib/commandReplay'\nimport { lockRoleWriterAuthorizationState } from '@open-mercato/core/modules/auth/lib/authorizationStateLocks'\n\ntype SerializedRole = {\n name: string\n tenantId: string\n custom?: Record<string, unknown>\n}\n\ntype RoleAclSnapshot = {\n id: string | null\n tenantId: string\n features: string[] | null\n isSuperAdmin: boolean\n organizations: string[] | null\n}\n\ntype RoleUndoSnapshot = {\n id: string\n name: string\n tenantId: string\n acls: RoleAclSnapshot[]\n custom?: Record<string, unknown>\n}\n\ntype RoleSnapshots = {\n view: SerializedRole\n undo: RoleUndoSnapshot\n}\n\nconst ROLE_REDO_LOG = Symbol('auth.roles.redoLog')\n\ntype RoleReplayRuntimeContext = CommandRuntimeContext & {\n [ROLE_REDO_LOG]?: CommandUndoLogEntry\n}\n\nfunction withRoleRedoLog(ctx: CommandRuntimeContext, logEntry: CommandUndoLogEntry): RoleReplayRuntimeContext {\n return { ...ctx, [ROLE_REDO_LOG]: logEntry }\n}\n\nfunction resolveCommandEm(ctx: CommandRuntimeContext, forkWhenUnbound = false): EntityManager {\n if (ctx.transactionalEm) return ctx.transactionalEm\n const em = ctx.container.resolve('em') as EntityManager\n return forkWhenUnbound ? em.fork() : em\n}\n\nfunction resolveActorTenantScope(ctx: CommandRuntimeContext): string | null {\n if (ctx.systemActor === true) return null\n const auth = ctx.auth\n if (!auth) return null\n if ((auth as { isSuperAdmin?: boolean }).isSuperAdmin === true) return null\n return normalizeTenantId(auth.tenantId ?? null) ?? null\n}\n\nfunction assertRoleTenantInScope(actorTenantScope: string | null, targetTenantId: unknown): void {\n if (!actorTenantScope) return\n const targetTenant = normalizeTenantId(targetTenantId) ?? null\n if (!targetTenant || targetTenant !== actorTenantScope) {\n throw new CrudHttpError(404, { error: 'Role not found' })\n }\n}\n\nconst RESERVED_ROLE_NAMES = new Set(['superadmin', 'admin'])\n\nfunction isReservedRoleName(name: string | undefined | null): boolean {\n if (typeof name !== 'string') return false\n const normalized = name.trim().toLowerCase()\n return normalized.length > 0 && RESERVED_ROLE_NAMES.has(normalized)\n}\n\nfunction assertRoleNameAllowed(name: string | undefined | null) {\n if (isReservedRoleName(name)) {\n throw new CrudHttpError(400, { error: 'Role name is reserved' })\n }\n}\n\ntype ResolvedActorScope = { isSuperAdmin: boolean; actorTenantId: string | null }\n\nasync function resolveActorScope(ctx: { auth: { tenantId?: string | null; sub?: string | null; orgId?: string | null; isSuperAdmin?: boolean } | null; container: { resolve<T = unknown>(name: string): T } }): Promise<ResolvedActorScope> {\n const isSuperAdmin = await resolveIsSuperAdmin(ctx)\n const actorTenantId = normalizeTenantId(ctx.auth?.tenantId ?? null) ?? null\n return { isSuperAdmin, actorTenantId }\n}\n\nfunction buildScopedRoleFilter(roleId: string, scope: ResolvedActorScope): FilterQuery<Role> {\n const filter: FilterQuery<Role> = { id: roleId, deletedAt: null }\n if (!scope.isSuperAdmin) {\n ;(filter as Record<string, unknown>).tenantId = scope.actorTenantId\n }\n return filter\n}\n\nconst createSchema = z.object({\n name: z.string().min(2).max(100),\n tenantId: z.string().uuid().optional(),\n})\n\nconst updateSchema = z.object({\n id: z.string().uuid(),\n name: z.string().min(2).max(100).optional(),\n tenantId: z.string().uuid().optional(),\n})\n\nexport const roleCrudEvents: CrudEventsConfig = {\n module: 'auth',\n entity: 'role',\n persistent: true,\n buildPayload: (ctx) => ({\n id: ctx.identifiers.id,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\nexport const roleCrudIndexer: CrudIndexerConfig = {\n entityType: E.auth.role,\n buildUpsertPayload: (ctx) => ({\n entityType: E.auth.role,\n recordId: ctx.identifiers.id,\n tenantId: ctx.identifiers.tenantId,\n }),\n buildDeletePayload: (ctx) => ({\n entityType: E.auth.role,\n recordId: ctx.identifiers.id,\n tenantId: ctx.identifiers.tenantId,\n }),\n}\n\nconst createRoleCommand: CommandHandler<Record<string, unknown>, Role> = {\n id: 'auth.roles.create',\n atomicReplay: true,\n stabilizeReplay: stabilizeRoleReplay,\n authorizeReplay: (params) => authorizeRoleReplay('create', params),\n async execute(rawInput, ctx) {\n const rawBody = rawInput && typeof rawInput === 'object' ? rawInput as Record<string, unknown> : {}\n if ('tenantId' in rawBody && rawBody.tenantId === null) {\n throw new CrudHttpError(400, { error: 'tenantId cannot be null \u2014 global roles are not supported' })\n }\n const { parsed, custom } = parseWithCustomFields(createSchema, rawInput)\n assertRoleNameAllowed(parsed.name)\n const scope = await resolveActorScope(ctx)\n const requestedTenantId = parsed.tenantId ?? null\n if (!scope.isSuperAdmin && requestedTenantId && requestedTenantId !== scope.actorTenantId) {\n throw new CrudHttpError(403, { error: 'Not authorized to target this tenant.' })\n }\n const resolvedTenantId = scope.isSuperAdmin\n ? (requestedTenantId ?? scope.actorTenantId)\n : scope.actorTenantId\n if (!resolvedTenantId) {\n throw new CrudHttpError(400, { error: 'tenantId is required \u2014 global roles are not supported' })\n }\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n let role: Role\n try {\n role = await de.createOrmEntity({\n entity: Role,\n data: {\n name: parsed.name,\n tenantId: resolvedTenantId,\n },\n })\n } catch (error) {\n if (!isUniqueViolation(error, 'roles_tenant_id_name_unique')) throw error\n const { translate } = await resolveTranslations()\n const message = translate('auth.roles.errors.nameExists', 'A role with this name already exists')\n throw new CrudHttpError(400, {\n error: message,\n fieldErrors: { name: message },\n details: [{ path: ['name'], message, code: 'duplicate', origin: 'validation' }],\n })\n }\n\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.role,\n recordId: String(role.id),\n organizationId: null,\n tenantId: resolvedTenantId,\n values: custom,\n })\n\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'created',\n entity: role,\n identifiers: {\n id: String(role.id),\n organizationId: null,\n tenantId: resolvedTenantId,\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n })\n\n return role\n },\n captureAfter: async (_input, result, ctx) => {\n const em = resolveCommandEm(ctx, true)\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n })\n return serializeRole(result, custom)\n },\n buildLog: async ({ result, ctx }) => {\n const { translate } = await resolveTranslations()\n const em = resolveCommandEm(ctx, true)\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n })\n const snapshot = captureRoleSnapshots(result, [], custom)\n return {\n actionLabel: translate('auth.audit.roles.create', 'Create role'),\n resourceKind: 'auth.role',\n resourceId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n snapshotAfter: snapshot.view,\n payload: {\n undo: {\n after: snapshot.undo,\n },\n },\n }\n },\n undo: async ({ input, logEntry, ctx }) => {\n const undo = extractUndoPayload<RoleUndoPayload>(logEntry)?.after\n if (!undo) return\n const em = resolveCommandEm(ctx)\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n await withAtomicFlush(em, [async () => {\n await authorizeRoleReplayAtMutation('create', {\n operation: 'undo',\n input,\n ctx,\n logEntry,\n })\n await lockRoleWriterAuthorizationState(em, [undo.id])\n await em.nativeDelete(RoleAcl, { role: undo.id as unknown as Role })\n if (undo.custom && Object.keys(undo.custom).length) {\n const reset = buildCustomFieldResetMap(undefined, undo.custom)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.role,\n recordId: undo.id,\n organizationId: null,\n tenantId: undo.tenantId ?? null,\n values: reset,\n notify: false,\n })\n }\n }\n await de.deleteOrmEntity({\n entity: Role,\n where: { id: undo.id, deletedAt: null } as FilterQuery<Role>,\n soft: false,\n })\n }], { transaction: true, label: 'auth.roles.create.undo' })\n },\n redo: async ({ input, logEntry, ctx }) => {\n const after = resolveRedoSnapshot<RoleUndoSnapshot>(logEntry)\n if (!after) throw new CrudHttpError(400, { error: '[internal] redo snapshot unavailable for role create' })\n const em = resolveCommandEm(ctx)\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n let role: Role | null = null\n await withAtomicFlush(em, [async () => {\n await authorizeRoleReplayAtMutation('create', {\n operation: 'redo',\n input,\n ctx,\n logEntry,\n })\n role = await findOneWithDecryption(em, Role, { id: after.id }, {}, { tenantId: null, organizationId: null })\n if (role) {\n role.deletedAt = null\n role.name = after.name\n role.tenantId = after.tenantId\n await em.flush()\n } else {\n role = await de.createOrmEntity({\n entity: Role,\n data: {\n id: after.id,\n name: after.name,\n tenantId: after.tenantId,\n },\n })\n }\n await restoreRoleAcls(em, after.id, after.acls)\n if (after.custom && Object.keys(after.custom).length) {\n const reset = buildCustomFieldResetMap(after.custom, undefined)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.role,\n recordId: after.id,\n organizationId: null,\n tenantId: after.tenantId ?? null,\n values: reset,\n notify: false,\n })\n }\n }\n }], { transaction: true, label: 'auth.roles.create.redo' })\n if (!role) throw new CrudHttpError(400, { error: '[internal] redo failed to restore role row' })\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'created',\n entity: role,\n identifiers: {\n id: after.id,\n organizationId: null,\n tenantId: after.tenantId ?? null,\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n })\n return role\n },\n}\n\nconst updateRoleCommand: CommandHandler<Record<string, unknown>, Role> = {\n id: 'auth.roles.update',\n atomicReplay: true,\n stabilizeReplay: stabilizeRoleReplay,\n authorizeReplay: (params) => authorizeRoleReplay('update', params),\n async prepare(rawInput, ctx) {\n const { parsed } = parseWithCustomFields(updateSchema, rawInput)\n const em = resolveCommandEm(ctx)\n const scope = await resolveActorScope(ctx)\n const existing = await findOneWithDecryption(em, Role, buildScopedRoleFilter(parsed.id, scope), {}, { tenantId: scope.actorTenantId, organizationId: null })\n if (!existing) throw new CrudHttpError(404, { error: 'Role not found' })\n assertRoleTenantInScope(resolveActorTenantScope(ctx), existing.tenantId)\n const acls = await loadRoleAclSnapshots(em, parsed.id)\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: parsed.id,\n tenantId: existing.tenantId ? String(existing.tenantId) : null,\n })\n return { before: captureRoleSnapshots(existing, acls, custom) }\n },\n async execute(rawInput, ctx) {\n const { parsed, custom } = parseWithCustomFields(updateSchema, rawInput)\n const em = resolveCommandEm(ctx)\n const scope = await resolveActorScope(ctx)\n const current = await findOneWithDecryption(em, Role, buildScopedRoleFilter(parsed.id, scope), {}, { tenantId: scope.actorTenantId, organizationId: null })\n if (!current) throw new CrudHttpError(404, { error: 'Role not found' })\n const actorTenantScope = resolveActorTenantScope(ctx)\n assertRoleTenantInScope(actorTenantScope, current.tenantId)\n if (parsed.name !== undefined) {\n const nextName = parsed.name\n if (nextName !== current.name) assertRoleNameAllowed(nextName)\n if (nextName !== current.name) {\n const assignments = await em.count(UserRole, { role: current, deletedAt: null })\n if (assignments > 0) {\n throw new CrudHttpError(400, { error: 'Role name cannot be changed while users are assigned' })\n }\n }\n }\n const wantsTenantChange = parsed.tenantId !== undefined && parsed.tenantId !== current.tenantId\n if (wantsTenantChange) {\n if (!scope.isSuperAdmin) {\n throw new CrudHttpError(403, { error: 'Not authorized to target this tenant.' })\n }\n const assignments = await em.count(UserRole, { role: current, deletedAt: null })\n if (assignments > 0) {\n throw new CrudHttpError(400, { error: 'Role cannot be moved to another tenant while users are assigned' })\n }\n }\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n const redoLogEntry = (ctx as RoleReplayRuntimeContext)[ROLE_REDO_LOG]\n let role: Role | null = null\n await withAtomicFlush(em, [async () => {\n if (redoLogEntry) {\n await authorizeRoleReplayAtMutation('update', {\n operation: 'redo',\n input: rawInput,\n ctx,\n logEntry: redoLogEntry,\n })\n }\n await lockRoleWriterAuthorizationState(em, [parsed.id])\n if (wantsTenantChange) {\n await em.nativeDelete(RoleAcl, { role: parsed.id as unknown as Role })\n }\n role = await de.updateOrmEntity({\n entity: Role,\n where: buildScopedRoleFilter(parsed.id, scope),\n apply: (entity) => {\n if (parsed.name !== undefined) entity.name = parsed.name\n if (parsed.tenantId !== undefined && scope.isSuperAdmin) entity.tenantId = parsed.tenantId\n },\n })\n if (!role) throw new CrudHttpError(404, { error: 'Role not found' })\n\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.role,\n recordId: String(role.id),\n organizationId: null,\n tenantId: role.tenantId ? String(role.tenantId) : null,\n values: custom,\n })\n }], {\n transaction: true,\n label: redoLogEntry ? 'auth.roles.update.redo' : 'auth.roles.update',\n })\n const updatedRole = role as Role | null\n if (!updatedRole) throw new CrudHttpError(404, { error: 'Role not found' })\n\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: updatedRole,\n identifiers: {\n id: String(updatedRole.id),\n organizationId: null,\n tenantId: updatedRole.tenantId ? String(updatedRole.tenantId) : null,\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n })\n\n return updatedRole\n },\n captureAfter: async (_input, result, ctx) => {\n const em = resolveCommandEm(ctx, true)\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n })\n return serializeRole(result, custom)\n },\n buildLog: async ({ result, snapshots, ctx }) => {\n const { translate } = await resolveTranslations()\n const beforeSnapshots = snapshots.before as RoleSnapshots | undefined\n const before = beforeSnapshots?.view\n const beforeUndo = beforeSnapshots?.undo ?? null\n const em = resolveCommandEm(ctx, true)\n const afterAcls = await loadRoleAclSnapshots(em, String(result.id))\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n })\n const afterSnapshots = captureRoleSnapshots(result, afterAcls, custom)\n const after = afterSnapshots.view\n const changes = buildChanges(before ?? null, after as Record<string, unknown>, ['name', 'tenantId'])\n const customDiff = diffCustomFieldChanges(before?.custom, custom)\n for (const [key, diff] of Object.entries(customDiff)) {\n changes[`cf_${key}`] = diff\n }\n return {\n actionLabel: translate('auth.audit.roles.update', 'Update role'),\n resourceKind: 'auth.role',\n resourceId: String(result.id),\n tenantId: result.tenantId ? String(result.tenantId) : null,\n changes,\n snapshotBefore: before ?? null,\n snapshotAfter: after,\n payload: {\n undo: {\n before: beforeUndo,\n after: afterSnapshots.undo,\n },\n },\n }\n },\n undo: async ({ input, logEntry, ctx }) => {\n const undo = extractUndoPayload<RoleUndoPayload>(logEntry)\n const before = undo?.before\n const after = undo?.after\n if (!before) return\n const storedReplayInput = extractStoredReplayInput(logEntry.commandPayload, logEntry)\n const replayInput = Object.keys(storedReplayInput).length\n ? storedReplayInput\n : deriveRoleUpdateReplayInput(before, after ?? null)\n const em = resolveCommandEm(ctx)\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n let updated: Role | null = null\n await withAtomicFlush(em, [async () => {\n await authorizeRoleReplayAtMutation('update', {\n operation: 'undo',\n input,\n ctx,\n logEntry,\n })\n updated = await de.updateOrmEntity({\n entity: Role,\n where: { id: before.id, deletedAt: null } as FilterQuery<Role>,\n apply: (entity) => {\n if ('name' in replayInput) entity.name = before.name\n if ('tenantId' in replayInput) entity.tenantId = before.tenantId\n },\n })\n const restoresAcls = 'tenantId' in replayInput && after?.tenantId !== before.tenantId\n if (updated && restoresAcls) {\n await restoreRoleAcls(em, before.id, before.acls)\n }\n const reset = buildCustomFieldResetMap(before.custom, after?.custom)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.role,\n recordId: before.id,\n organizationId: null,\n tenantId: before.tenantId,\n values: reset,\n notify: false,\n })\n }\n }], { transaction: true, label: 'auth.roles.update.undo' })\n await emitCrudUndoSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: updated,\n identifiers: {\n id: before.id,\n organizationId: null,\n tenantId: before.tenantId ?? null,\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n })\n },\n redo: ({ input, ctx, logEntry }) => updateRoleCommand.execute(input, withRoleRedoLog(ctx, logEntry)),\n}\n\nconst deleteRoleCommand: CommandHandler<{ body?: Record<string, unknown>; query?: Record<string, unknown> }, Role> = {\n id: 'auth.roles.delete',\n atomicReplay: true,\n stabilizeReplay: stabilizeRoleReplay,\n authorizeReplay: (params) => authorizeRoleReplay('delete', params),\n async prepare(input, ctx) {\n const id = requireId(input, 'Role id required')\n const em = resolveCommandEm(ctx)\n const scope = await resolveActorScope(ctx)\n const existing = await findOneWithDecryption(em, Role, buildScopedRoleFilter(id, scope), {}, { tenantId: scope.actorTenantId, organizationId: null })\n if (!existing) return {}\n const actorTenantScope = resolveActorTenantScope(ctx)\n if (actorTenantScope) {\n const targetTenant = normalizeTenantId(existing.tenantId) ?? null\n if (!targetTenant || targetTenant !== actorTenantScope) return {}\n }\n const acls = await loadRoleAclSnapshots(em, id)\n const custom = await loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: id,\n tenantId: existing.tenantId ? String(existing.tenantId) : null,\n })\n return { before: captureRoleSnapshots(existing, acls, custom) }\n },\n async execute(input, ctx) {\n const id = requireId(input, 'Role id required')\n const em = resolveCommandEm(ctx)\n const scope = await resolveActorScope(ctx)\n const role = await findOneWithDecryption(em, Role, buildScopedRoleFilter(id, scope), {}, { tenantId: scope.actorTenantId, organizationId: null })\n if (!role) throw new CrudHttpError(404, { error: 'Role not found' })\n assertRoleTenantInScope(resolveActorTenantScope(ctx), role.tenantId)\n\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n const redoLogEntry = (ctx as RoleReplayRuntimeContext)[ROLE_REDO_LOG]\n let deleted: Role | null = null\n await withAtomicFlush(em, [async () => {\n if (redoLogEntry) {\n await authorizeRoleReplayAtMutation('delete', {\n operation: 'redo',\n input,\n ctx,\n logEntry: redoLogEntry,\n })\n }\n await lockRoleWriterAuthorizationState(em, [id])\n const activeAssignments = await em.count(UserRole, { role, deletedAt: null })\n if (activeAssignments > 0) throw new CrudHttpError(400, { error: 'Role has assigned users' })\n await em.nativeDelete(RoleAcl, { role: id })\n deleted = await de.deleteOrmEntity({\n entity: Role,\n where: buildScopedRoleFilter(id, scope),\n soft: false,\n })\n if (!deleted) throw new CrudHttpError(404, { error: 'Role not found' })\n }], {\n transaction: true,\n label: redoLogEntry ? 'auth.roles.delete.redo' : 'auth.roles.delete',\n })\n const deletedRole = deleted as Role | null\n if (!deletedRole) throw new CrudHttpError(404, { error: 'Role not found' })\n\n await emitCrudSideEffects({\n dataEngine: de,\n action: 'deleted',\n entity: deletedRole,\n identifiers: {\n id,\n organizationId: null,\n tenantId: deletedRole.tenantId ? String(deletedRole.tenantId) : null,\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n })\n\n return deletedRole\n },\n buildLog: async ({ snapshots, input }) => {\n const { translate } = await resolveTranslations()\n const beforeSnapshots = snapshots.before as RoleSnapshots | undefined\n const before = beforeSnapshots?.view\n const beforeUndo = beforeSnapshots?.undo ?? null\n const id = requireId(input, 'Role id required')\n return {\n actionLabel: translate('auth.audit.roles.delete', 'Delete role'),\n resourceKind: 'auth.role',\n resourceId: id,\n tenantId: before?.tenantId ?? null,\n snapshotBefore: before ?? null,\n payload: {\n undo: {\n before: beforeUndo,\n },\n },\n }\n },\n undo: async ({ input, logEntry, ctx }) => {\n const before = extractUndoPayload<RoleUndoPayload>(logEntry)?.before\n if (!before) return\n const em = resolveCommandEm(ctx)\n const de = (ctx.container.resolve('dataEngine') as DataEngine)\n let role: Role | null = null\n await withAtomicFlush(em, [async () => {\n await authorizeRoleReplayAtMutation('delete', {\n operation: 'undo',\n input,\n ctx,\n logEntry,\n })\n role = await findOneWithDecryption(em, Role, { id: before.id }, {}, { tenantId: null, organizationId: null })\n if (role) {\n role.deletedAt = null\n role.name = before.name\n role.tenantId = before.tenantId\n await em.flush()\n } else {\n role = await de.createOrmEntity({\n entity: Role,\n data: {\n id: before.id,\n name: before.name,\n tenantId: before.tenantId,\n },\n })\n }\n await restoreRoleAcls(em, before.id, before.acls)\n const reset = buildCustomFieldResetMap(before.custom, undefined)\n if (Object.keys(reset).length) {\n await setCustomFieldsIfAny({\n dataEngine: de,\n entityId: E.auth.role,\n recordId: before.id,\n organizationId: null,\n tenantId: before.tenantId ?? null,\n values: reset,\n notify: false,\n })\n }\n }], { transaction: true, label: 'auth.roles.delete.undo' })\n if (!role) throw new CrudHttpError(400, { error: '[internal] undo failed to restore role row' })\n await emitCrudUndoSideEffects({\n dataEngine: de,\n action: 'updated',\n entity: role,\n identifiers: {\n id: before.id,\n organizationId: null,\n tenantId: before.tenantId ?? null,\n },\n events: roleCrudEvents,\n indexer: roleCrudIndexer,\n })\n },\n redo: ({ input, ctx, logEntry }) => deleteRoleCommand.execute(input, withRoleRedoLog(ctx, logEntry)),\n}\n\ntype RoleReplayCommandKind = 'create' | 'update' | 'delete'\n\nfunction normalizeRoleAclSnapshots(acls: RoleAclSnapshot[]): RoleAclSnapshot[] {\n return acls\n .map((acl) => ({\n ...acl,\n features: acl.features ? [...acl.features].sort((left, right) => left.localeCompare(right)) : null,\n organizations: acl.organizations ? [...acl.organizations].sort((left, right) => left.localeCompare(right)) : null,\n }))\n .sort((left, right) => {\n const tenantOrder = left.tenantId.localeCompare(right.tenantId)\n if (tenantOrder !== 0) return tenantOrder\n return (left.id ?? '').localeCompare(right.id ?? '')\n })\n}\n\nfunction normalizeRoleReplaySnapshot(snapshot: RoleUndoSnapshot): RoleUndoSnapshot {\n return {\n ...snapshot,\n acls: normalizeRoleAclSnapshots(snapshot.acls),\n }\n}\n\nfunction toRoleUpdateReplayState(snapshot: RoleUndoSnapshot): SerializedRole {\n return {\n name: snapshot.name,\n tenantId: snapshot.tenantId,\n ...(snapshot.custom && Object.keys(snapshot.custom).length ? { custom: snapshot.custom } : {}),\n }\n}\n\nfunction deriveRoleUpdateReplayInput(\n before: RoleUndoSnapshot | null,\n after: RoleUndoSnapshot | null,\n): Record<string, unknown> {\n if (!before) return {}\n if (!after) return { id: before.id, name: before.name, tenantId: before.tenantId }\n const input: Record<string, unknown> = { id: after.id }\n if (before.name !== after.name) input.name = after.name\n if (before.tenantId !== after.tenantId) input.tenantId = after.tenantId\n return input\n}\n\nasync function authorizeRoleReplay(\n commandKind: RoleReplayCommandKind,\n params: CommandReplayAuthorizationArgs<unknown>,\n): Promise<void> {\n const { operation, ctx, logEntry } = params\n const em = requireTransactionalReplayEntityManager(ctx)\n await requireCurrentReplayFeature(ctx, 'auth.roles.manage')\n\n const undoPayload = extractUndoPayload<RoleUndoPayload>(logEntry)\n const before = undoPayload?.before ?? null\n const after = undoPayload?.after ?? null\n const roleId = logEntry.resourceId ?? before?.id ?? after?.id ?? null\n if (!roleId) {\n await assertReplaySnapshotMatches(null, { required: 'role replay snapshot' })\n return\n }\n\n const currentRole = await findOneWithDecryption(\n em,\n Role,\n { id: roleId, deletedAt: null },\n {},\n { tenantId: null, organizationId: null },\n )\n const rbacService = ctx.systemActor === true\n ? null\n : ctx.container.resolve('rbacService') as RbacService\n if (rbacService && currentRole) {\n await assertActorCanAccessRoleTarget({\n em,\n rbacService,\n requireBoundRbac: true,\n actorUserId: ctx.auth?.sub,\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.auth?.orgId ?? null,\n targetRoleId: roleId,\n })\n await assertActorCanModifySuperAdminRoleTarget({\n em,\n rbacService,\n requireBoundRbac: true,\n actorUserId: ctx.auth?.sub,\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.auth?.orgId ?? null,\n targetRoleId: roleId,\n })\n }\n\n let currentSnapshot: RoleUndoSnapshot | null = null\n if (currentRole) {\n const currentTenantId = currentRole.tenantId ? String(currentRole.tenantId) : null\n const [acls, custom] = await Promise.all([\n loadRoleAclSnapshots(em, roleId),\n loadCustomFieldSnapshot(em, {\n entityId: E.auth.role,\n recordId: roleId,\n tenantId: currentTenantId,\n }),\n ])\n currentSnapshot = captureRoleSnapshots(currentRole, acls, custom).undo\n }\n\n const expectedCurrent = operation === 'undo'\n ? commandKind === 'create' ? after : commandKind === 'update' ? after : null\n : commandKind === 'create' ? null : before\n const storedReplayInput = extractStoredReplayInput(params.input, logEntry)\n const replayInput = Object.keys(storedReplayInput).length\n ? storedReplayInput\n : deriveRoleUpdateReplayInput(before, after)\n const movesTenant = commandKind === 'update'\n && 'tenantId' in replayInput\n && before?.tenantId !== after?.tenantId\n if (movesTenant) {\n await requireCurrentReplaySuperAdmin(ctx)\n }\n\n if (commandKind === 'update' && !movesTenant) {\n if (!currentSnapshot || !expectedCurrent) {\n await assertReplaySnapshotMatches(currentSnapshot, expectedCurrent)\n } else {\n await assertReplaySnapshotMatches(\n toRoleUpdateReplayState(currentSnapshot),\n toRoleUpdateReplayState(expectedCurrent),\n )\n }\n } else {\n await assertReplaySnapshotMatches(\n currentSnapshot ? normalizeRoleReplaySnapshot(currentSnapshot) : null,\n expectedCurrent ? normalizeRoleReplaySnapshot(expectedCurrent) : null,\n )\n }\n\n const desiredState = operation === 'undo'\n ? commandKind === 'create' ? null : before\n : commandKind === 'delete' ? null : after\n if (desiredState && !currentRole && rbacService) {\n const actorUserId = ctx.auth?.sub\n if (!actorUserId) throw forbidden()\n const actorAcl = await rbacService.loadAclWithEntityManager(em, actorUserId, {\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.auth?.orgId ?? null,\n })\n if (!actorAcl.isSuperAdmin) assertRoleTenantInScope(ctx.auth?.tenantId ?? null, desiredState.tenantId)\n }\n const restoresAcls = Boolean(\n desiredState\n && desiredState.acls.length > 0\n && (commandKind !== 'update' || movesTenant),\n )\n if (!restoresAcls || !desiredState || !rbacService) return\n\n await requireCurrentReplayFeature(ctx, 'auth.acl.manage')\n for (const acl of desiredState.acls) {\n await assertActorCanGrantAcl({\n em,\n rbacService,\n requireBoundRbac: true,\n actorUserId: ctx.auth?.sub,\n tenantId: acl.tenantId,\n organizationId: null,\n features: acl.features,\n isSuperAdmin: acl.isSuperAdmin,\n organizations: acl.organizations,\n })\n }\n}\n\nasync function authorizeRoleReplayAtMutation(\n commandKind: RoleReplayCommandKind,\n params: CommandReplayAuthorizationArgs<unknown>,\n): Promise<void> {\n const em = params.ctx.transactionalEm\n ?? params.ctx.container.resolve('em') as EntityManager\n const boundParams = params.ctx.transactionalEm\n ? params\n : { ...params, ctx: { ...params.ctx, transactionalEm: em } }\n await stabilizeRoleReplay(boundParams)\n await rerunReplayTransactionGuardAfterLocks(boundParams, em)\n await authorizeRoleReplay(commandKind, boundParams)\n}\n\nasync function stabilizeRoleReplay(\n params: CommandReplayAuthorizationArgs<unknown>,\n): Promise<void> {\n const undoPayload = extractUndoPayload<RoleUndoPayload>(params.logEntry)\n const before = undoPayload?.before ?? null\n const after = undoPayload?.after ?? null\n const targetRoleId = params.logEntry.resourceId ?? before?.id ?? after?.id ?? null\n const em = requireTransactionalReplayEntityManager(params.ctx)\n await lockReplayAuthorizationState(em, params.ctx, {\n targetRoleIds: targetRoleId ? [targetRoleId] : [],\n targetTenantIds: [\n ...(before?.tenantId ? [before.tenantId] : []),\n ...(after?.tenantId ? [after.tenantId] : []),\n ...(params.logEntry.tenantId ? [params.logEntry.tenantId] : []),\n ],\n })\n}\n\nregisterCommand(createRoleCommand)\nregisterCommand(updateRoleCommand)\nregisterCommand(deleteRoleCommand)\n\nfunction serializeRole(role: Role, custom?: Record<string, unknown> | null): SerializedRole {\n const payload: SerializedRole = {\n name: String(role.name ?? ''),\n tenantId: String(role.tenantId),\n }\n if (custom && Object.keys(custom).length) payload.custom = custom\n return payload\n}\n\nfunction captureRoleSnapshots(\n role: Role,\n acls: RoleAclSnapshot[] = [],\n custom?: Record<string, unknown> | null\n): RoleSnapshots {\n return {\n view: serializeRole(role, custom),\n undo: {\n id: String(role.id),\n name: String(role.name ?? ''),\n tenantId: String(role.tenantId),\n acls,\n ...(custom && Object.keys(custom).length ? { custom } : {}),\n },\n }\n}\n\nasync function loadRoleAclSnapshots(em: EntityManager, roleId: string): Promise<RoleAclSnapshot[]> {\n const entries = await findWithDecryption(em, RoleAcl, { role: roleId as unknown as Role, deletedAt: null }, {}, { tenantId: null, organizationId: null })\n return entries.map((entry) => ({\n id: entry.id ? String(entry.id) : null,\n tenantId: String(entry.tenantId),\n features: Array.isArray(entry.featuresJson) ? [...entry.featuresJson] : null,\n isSuperAdmin: Boolean(entry.isSuperAdmin),\n organizations: Array.isArray(entry.organizationsJson) ? [...entry.organizationsJson] : null,\n }))\n}\n\nasync function restoreRoleAcls(em: EntityManager, roleId: string, acls: RoleAclSnapshot[]) {\n await lockRoleWriterAuthorizationState(em, [roleId])\n await em.nativeDelete(RoleAcl, { role: roleId as unknown as Role })\n if (!acls.length) {\n await em.flush()\n return\n }\n const roleRef = em.getReference(Role, roleId)\n for (const acl of acls) {\n const entity = em.create(RoleAcl, {\n id: acl.id ?? undefined,\n role: roleRef,\n tenantId: acl.tenantId,\n featuresJson: acl.features ?? null,\n isSuperAdmin: acl.isSuperAdmin,\n organizationsJson: acl.organizations ?? null,\n createdAt: new Date(),\n })\n em.persist(entity)\n }\n await em.flush()\n}\n\ntype RoleUndoPayload = { before?: RoleUndoSnapshot | null; after?: RoleUndoSnapshot | null }\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,uBAAuB;AAChC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,eAAe,WAAW,yBAAyB;AAC5D,SAAS,2BAA2B;AAEpC,SAAS,uBAAuB,0BAA0B;AAE1D,SAAS,SAAS;AAClB,SAAS,MAAM,SAAS,gBAAgB;AACxC,SAAS,SAAS;AAClB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,0BAA0B;AACnC,SAAS,2BAA2B;AACpC,SAAS,uBAAuB;AAChC,SAAS,qBAAqB,yBAAyB;AAEvD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,wCAAwC;AA6BjD,MAAM,gBAAgB,uBAAO,oBAAoB;AAMjD,SAAS,gBAAgB,KAA4B,UAAyD;AAC5G,SAAO,EAAE,GAAG,KAAK,CAAC,aAAa,GAAG,SAAS;AAC7C;AAEA,SAAS,iBAAiB,KAA4B,kBAAkB,OAAsB;AAC5F,MAAI,IAAI,gBAAiB,QAAO,IAAI;AACpC,QAAM,KAAK,IAAI,UAAU,QAAQ,IAAI;AACrC,SAAO,kBAAkB,GAAG,KAAK,IAAI;AACvC;AAEA,SAAS,wBAAwB,KAA2C;AAC1E,MAAI,IAAI,gBAAgB,KAAM,QAAO;AACrC,QAAM,OAAO,IAAI;AACjB,MAAI,CAAC,KAAM,QAAO;AAClB,MAAK,KAAoC,iBAAiB,KAAM,QAAO;AACvE,SAAO,kBAAkB,KAAK,YAAY,IAAI,KAAK;AACrD;AAEA,SAAS,wBAAwB,kBAAiC,gBAA+B;AAC/F,MAAI,CAAC,iBAAkB;AACvB,QAAM,eAAe,kBAAkB,cAAc,KAAK;AAC1D,MAAI,CAAC,gBAAgB,iBAAiB,kBAAkB;AACtD,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,iBAAiB,CAAC;AAAA,EAC1D;AACF;AAEA,MAAM,sBAAsB,oBAAI,IAAI,CAAC,cAAc,OAAO,CAAC;AAE3D,SAAS,mBAAmB,MAA0C;AACpE,MAAI,OAAO,SAAS,SAAU,QAAO;AACrC,QAAM,aAAa,KAAK,KAAK,EAAE,YAAY;AAC3C,SAAO,WAAW,SAAS,KAAK,oBAAoB,IAAI,UAAU;AACpE;AAEA,SAAS,sBAAsB,MAAiC;AAC9D,MAAI,mBAAmB,IAAI,GAAG;AAC5B,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,wBAAwB,CAAC;AAAA,EACjE;AACF;AAIA,eAAe,kBAAkB,KAA2M;AAC1O,QAAM,eAAe,MAAM,oBAAoB,GAAG;AAClD,QAAM,gBAAgB,kBAAkB,IAAI,MAAM,YAAY,IAAI,KAAK;AACvE,SAAO,EAAE,cAAc,cAAc;AACvC;AAEA,SAAS,sBAAsB,QAAgB,OAA8C;AAC3F,QAAM,SAA4B,EAAE,IAAI,QAAQ,WAAW,KAAK;AAChE,MAAI,CAAC,MAAM,cAAc;AACvB;AAAC,IAAC,OAAmC,WAAW,MAAM;AAAA,EACxD;AACA,SAAO;AACT;AAEA,MAAM,eAAe,EAAE,OAAO;AAAA,EAC5B,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG;AAAA,EAC/B,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AACvC,CAAC;AAED,MAAM,eAAe,EAAE,OAAO;AAAA,EAC5B,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,EACpB,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EAC1C,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AACvC,CAAC;AAEM,MAAM,iBAAmC;AAAA,EAC9C,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,cAAc,CAAC,SAAS;AAAA,IACtB,IAAI,IAAI,YAAY;AAAA,IACpB,UAAU,IAAI,YAAY;AAAA,EAC5B;AACF;AAEO,MAAM,kBAAqC;AAAA,EAChD,YAAY,EAAE,KAAK;AAAA,EACnB,oBAAoB,CAAC,SAAS;AAAA,IAC5B,YAAY,EAAE,KAAK;AAAA,IACnB,UAAU,IAAI,YAAY;AAAA,IAC1B,UAAU,IAAI,YAAY;AAAA,EAC5B;AAAA,EACA,oBAAoB,CAAC,SAAS;AAAA,IAC5B,YAAY,EAAE,KAAK;AAAA,IACnB,UAAU,IAAI,YAAY;AAAA,IAC1B,UAAU,IAAI,YAAY;AAAA,EAC5B;AACF;AAEA,MAAM,oBAAmE;AAAA,EACvE,IAAI;AAAA,EACJ,cAAc;AAAA,EACd,iBAAiB;AAAA,EACjB,iBAAiB,CAAC,WAAW,oBAAoB,UAAU,MAAM;AAAA,EACjE,MAAM,QAAQ,UAAU,KAAK;AAC3B,UAAM,UAAU,YAAY,OAAO,aAAa,WAAW,WAAsC,CAAC;AAClG,QAAI,cAAc,WAAW,QAAQ,aAAa,MAAM;AACtD,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,gEAA2D,CAAC;AAAA,IACpG;AACA,UAAM,EAAE,QAAQ,OAAO,IAAI,sBAAsB,cAAc,QAAQ;AACvE,0BAAsB,OAAO,IAAI;AACjC,UAAM,QAAQ,MAAM,kBAAkB,GAAG;AACzC,UAAM,oBAAoB,OAAO,YAAY;AAC7C,QAAI,CAAC,MAAM,gBAAgB,qBAAqB,sBAAsB,MAAM,eAAe;AACzF,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,wCAAwC,CAAC;AAAA,IACjF;AACA,UAAM,mBAAmB,MAAM,eAC1B,qBAAqB,MAAM,gBAC5B,MAAM;AACV,QAAI,CAAC,kBAAkB;AACrB,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,6DAAwD,CAAC;AAAA,IACjG;AACA,UAAM,KAAM,IAAI,UAAU,QAAQ,YAAY;AAC9C,QAAI;AACJ,QAAI;AACF,aAAO,MAAM,GAAG,gBAAgB;AAAA,QAC9B,QAAQ;AAAA,QACR,MAAM;AAAA,UACJ,MAAM,OAAO;AAAA,UACb,UAAU;AAAA,QACZ;AAAA,MACF,CAAC;AAAA,IACH,SAAS,OAAO;AACd,UAAI,CAAC,kBAAkB,OAAO,6BAA6B,EAAG,OAAM;AACpE,YAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,YAAM,UAAU,UAAU,gCAAgC,sCAAsC;AAChG,YAAM,IAAI,cAAc,KAAK;AAAA,QAC3B,OAAO;AAAA,QACP,aAAa,EAAE,MAAM,QAAQ;AAAA,QAC7B,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,GAAG,SAAS,MAAM,aAAa,QAAQ,aAAa,CAAC;AAAA,MAChF,CAAC;AAAA,IACH;AAEA,UAAM,qBAAqB;AAAA,MACzB,YAAY;AAAA,MACZ,UAAU,EAAE,KAAK;AAAA,MACjB,UAAU,OAAO,KAAK,EAAE;AAAA,MACxB,gBAAgB;AAAA,MAChB,UAAU;AAAA,MACV,QAAQ;AAAA,IACV,CAAC;AAED,UAAM,oBAAoB;AAAA,MACxB,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa;AAAA,QACX,IAAI,OAAO,KAAK,EAAE;AAAA,QAClB,gBAAgB;AAAA,QAChB,UAAU;AAAA,MACZ;AAAA,MACA,QAAQ;AAAA,MACR,SAAS;AAAA,IACX,CAAC;AAED,WAAO;AAAA,EACT;AAAA,EACA,cAAc,OAAO,QAAQ,QAAQ,QAAQ;AAC3C,UAAM,KAAK,iBAAiB,KAAK,IAAI;AACrC,UAAM,SAAS,MAAM,wBAAwB,IAAI;AAAA,MAC/C,UAAU,EAAE,KAAK;AAAA,MACjB,UAAU,OAAO,OAAO,EAAE;AAAA,MAC1B,UAAU,OAAO,WAAW,OAAO,OAAO,QAAQ,IAAI;AAAA,IACxD,CAAC;AACD,WAAO,cAAc,QAAQ,MAAM;AAAA,EACrC;AAAA,EACA,UAAU,OAAO,EAAE,QAAQ,IAAI,MAAM;AACnC,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,UAAM,KAAK,iBAAiB,KAAK,IAAI;AACrC,UAAM,SAAS,MAAM,wBAAwB,IAAI;AAAA,MAC/C,UAAU,EAAE,KAAK;AAAA,MACjB,UAAU,OAAO,OAAO,EAAE;AAAA,MAC1B,UAAU,OAAO,WAAW,OAAO,OAAO,QAAQ,IAAI;AAAA,IACxD,CAAC;AACD,UAAM,WAAW,qBAAqB,QAAQ,CAAC,GAAG,MAAM;AACxD,WAAO;AAAA,MACL,aAAa,UAAU,2BAA2B,aAAa;AAAA,MAC/D,cAAc;AAAA,MACd,YAAY,OAAO,OAAO,EAAE;AAAA,MAC5B,UAAU,OAAO,WAAW,OAAO,OAAO,QAAQ,IAAI;AAAA,MACtD,eAAe,SAAS;AAAA,MACxB,SAAS;AAAA,QACP,MAAM;AAAA,UACJ,OAAO,SAAS;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EACA,MAAM,OAAO,EAAE,OAAO,UAAU,IAAI,MAAM;AACxC,UAAM,OAAO,mBAAoC,QAAQ,GAAG;AAC5D,QAAI,CAAC,KAAM;AACX,UAAM,KAAK,iBAAiB,GAAG;AAC/B,UAAM,KAAM,IAAI,UAAU,QAAQ,YAAY;AAC9C,UAAM,gBAAgB,IAAI,CAAC,YAAY;AACrC,YAAM,8BAA8B,UAAU;AAAA,QAC5C,WAAW;AAAA,QACX;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AACD,YAAM,iCAAiC,IAAI,CAAC,KAAK,EAAE,CAAC;AACpD,YAAM,GAAG,aAAa,SAAS,EAAE,MAAM,KAAK,GAAsB,CAAC;AACnE,UAAI,KAAK,UAAU,OAAO,KAAK,KAAK,MAAM,EAAE,QAAQ;AAClD,cAAM,QAAQ,yBAAyB,QAAW,KAAK,MAAM;AAC7D,YAAI,OAAO,KAAK,KAAK,EAAE,QAAQ;AAC7B,gBAAM,qBAAqB;AAAA,YACzB,YAAY;AAAA,YACZ,UAAU,EAAE,KAAK;AAAA,YACjB,UAAU,KAAK;AAAA,YACf,gBAAgB;AAAA,YAChB,UAAU,KAAK,YAAY;AAAA,YAC3B,QAAQ;AAAA,YACR,QAAQ;AAAA,UACV,CAAC;AAAA,QACH;AAAA,MACF;AACA,YAAM,GAAG,gBAAgB;AAAA,QACvB,QAAQ;AAAA,QACR,OAAO,EAAE,IAAI,KAAK,IAAI,WAAW,KAAK;AAAA,QACtC,MAAM;AAAA,MACR,CAAC;AAAA,IACH,CAAC,GAAG,EAAE,aAAa,MAAM,OAAO,yBAAyB,CAAC;AAAA,EAC5D;AAAA,EACA,MAAM,OAAO,EAAE,OAAO,UAAU,IAAI,MAAM;AACxC,UAAM,QAAQ,oBAAsC,QAAQ;AAC5D,QAAI,CAAC,MAAO,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,uDAAuD,CAAC;AAC1G,UAAM,KAAK,iBAAiB,GAAG;AAC/B,UAAM,KAAM,IAAI,UAAU,QAAQ,YAAY;AAC9C,QAAI,OAAoB;AACxB,UAAM,gBAAgB,IAAI,CAAC,YAAY;AACrC,YAAM,8BAA8B,UAAU;AAAA,QAC5C,WAAW;AAAA,QACX;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AACD,aAAO,MAAM,sBAAsB,IAAI,MAAM,EAAE,IAAI,MAAM,GAAG,GAAG,CAAC,GAAG,EAAE,UAAU,MAAM,gBAAgB,KAAK,CAAC;AAC3G,UAAI,MAAM;AACR,aAAK,YAAY;AACjB,aAAK,OAAO,MAAM;AAClB,aAAK,WAAW,MAAM;AACtB,cAAM,GAAG,MAAM;AAAA,MACjB,OAAO;AACL,eAAO,MAAM,GAAG,gBAAgB;AAAA,UAC9B,QAAQ;AAAA,UACR,MAAM;AAAA,YACJ,IAAI,MAAM;AAAA,YACV,MAAM,MAAM;AAAA,YACZ,UAAU,MAAM;AAAA,UAClB;AAAA,QACF,CAAC;AAAA,MACH;AACA,YAAM,gBAAgB,IAAI,MAAM,IAAI,MAAM,IAAI;AAC9C,UAAI,MAAM,UAAU,OAAO,KAAK,MAAM,MAAM,EAAE,QAAQ;AACpD,cAAM,QAAQ,yBAAyB,MAAM,QAAQ,MAAS;AAC9D,YAAI,OAAO,KAAK,KAAK,EAAE,QAAQ;AAC7B,gBAAM,qBAAqB;AAAA,YACzB,YAAY;AAAA,YACZ,UAAU,EAAE,KAAK;AAAA,YACjB,UAAU,MAAM;AAAA,YAChB,gBAAgB;AAAA,YAChB,UAAU,MAAM,YAAY;AAAA,YAC5B,QAAQ;AAAA,YACR,QAAQ;AAAA,UACV,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,CAAC,GAAG,EAAE,aAAa,MAAM,OAAO,yBAAyB,CAAC;AAC1D,QAAI,CAAC,KAAM,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,6CAA6C,CAAC;AAC/F,UAAM,oBAAoB;AAAA,MACxB,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa;AAAA,QACX,IAAI,MAAM;AAAA,QACV,gBAAgB;AAAA,QAChB,UAAU,MAAM,YAAY;AAAA,MAC9B;AAAA,MACA,QAAQ;AAAA,MACR,SAAS;AAAA,IACX,CAAC;AACD,WAAO;AAAA,EACT;AACF;AAEA,MAAM,oBAAmE;AAAA,EACvE,IAAI;AAAA,EACJ,cAAc;AAAA,EACd,iBAAiB;AAAA,EACjB,iBAAiB,CAAC,WAAW,oBAAoB,UAAU,MAAM;AAAA,EACjE,MAAM,QAAQ,UAAU,KAAK;AAC3B,UAAM,EAAE,OAAO,IAAI,sBAAsB,cAAc,QAAQ;AAC/D,UAAM,KAAK,iBAAiB,GAAG;AAC/B,UAAM,QAAQ,MAAM,kBAAkB,GAAG;AACzC,UAAM,WAAW,MAAM,sBAAsB,IAAI,MAAM,sBAAsB,OAAO,IAAI,KAAK,GAAG,CAAC,GAAG,EAAE,UAAU,MAAM,eAAe,gBAAgB,KAAK,CAAC;AAC3J,QAAI,CAAC,SAAU,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,iBAAiB,CAAC;AACvE,4BAAwB,wBAAwB,GAAG,GAAG,SAAS,QAAQ;AACvE,UAAM,OAAO,MAAM,qBAAqB,IAAI,OAAO,EAAE;AACrD,UAAM,SAAS,MAAM,wBAAwB,IAAI;AAAA,MAC/C,UAAU,EAAE,KAAK;AAAA,MACjB,UAAU,OAAO;AAAA,MACjB,UAAU,SAAS,WAAW,OAAO,SAAS,QAAQ,IAAI;AAAA,IAC5D,CAAC;AACD,WAAO,EAAE,QAAQ,qBAAqB,UAAU,MAAM,MAAM,EAAE;AAAA,EAChE;AAAA,EACA,MAAM,QAAQ,UAAU,KAAK;AAC3B,UAAM,EAAE,QAAQ,OAAO,IAAI,sBAAsB,cAAc,QAAQ;AACvE,UAAM,KAAK,iBAAiB,GAAG;AAC/B,UAAM,QAAQ,MAAM,kBAAkB,GAAG;AACzC,UAAM,UAAU,MAAM,sBAAsB,IAAI,MAAM,sBAAsB,OAAO,IAAI,KAAK,GAAG,CAAC,GAAG,EAAE,UAAU,MAAM,eAAe,gBAAgB,KAAK,CAAC;AAC1J,QAAI,CAAC,QAAS,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,iBAAiB,CAAC;AACtE,UAAM,mBAAmB,wBAAwB,GAAG;AACpD,4BAAwB,kBAAkB,QAAQ,QAAQ;AAC1D,QAAI,OAAO,SAAS,QAAW;AAC7B,YAAM,WAAW,OAAO;AACxB,UAAI,aAAa,QAAQ,KAAM,uBAAsB,QAAQ;AAC7D,UAAI,aAAa,QAAQ,MAAM;AAC7B,cAAM,cAAc,MAAM,GAAG,MAAM,UAAU,EAAE,MAAM,SAAS,WAAW,KAAK,CAAC;AAC/E,YAAI,cAAc,GAAG;AACnB,gBAAM,IAAI,cAAc,KAAK,EAAE,OAAO,uDAAuD,CAAC;AAAA,QAChG;AAAA,MACF;AAAA,IACF;AACA,UAAM,oBAAoB,OAAO,aAAa,UAAa,OAAO,aAAa,QAAQ;AACvF,QAAI,mBAAmB;AACrB,UAAI,CAAC,MAAM,cAAc;AACvB,cAAM,IAAI,cAAc,KAAK,EAAE,OAAO,wCAAwC,CAAC;AAAA,MACjF;AACA,YAAM,cAAc,MAAM,GAAG,MAAM,UAAU,EAAE,MAAM,SAAS,WAAW,KAAK,CAAC;AAC/E,UAAI,cAAc,GAAG;AACnB,cAAM,IAAI,cAAc,KAAK,EAAE,OAAO,kEAAkE,CAAC;AAAA,MAC3G;AAAA,IACF;AACA,UAAM,KAAM,IAAI,UAAU,QAAQ,YAAY;AAC9C,UAAM,eAAgB,IAAiC,aAAa;AACpE,QAAI,OAAoB;AACxB,UAAM,gBAAgB,IAAI,CAAC,YAAY;AACrC,UAAI,cAAc;AAChB,cAAM,8BAA8B,UAAU;AAAA,UAC5C,WAAW;AAAA,UACX,OAAO;AAAA,UACP;AAAA,UACA,UAAU;AAAA,QACZ,CAAC;AAAA,MACH;AACA,YAAM,iCAAiC,IAAI,CAAC,OAAO,EAAE,CAAC;AACtD,UAAI,mBAAmB;AACrB,cAAM,GAAG,aAAa,SAAS,EAAE,MAAM,OAAO,GAAsB,CAAC;AAAA,MACvE;AACA,aAAO,MAAM,GAAG,gBAAgB;AAAA,QAC9B,QAAQ;AAAA,QACR,OAAO,sBAAsB,OAAO,IAAI,KAAK;AAAA,QAC7C,OAAO,CAAC,WAAW;AACjB,cAAI,OAAO,SAAS,OAAW,QAAO,OAAO,OAAO;AACpD,cAAI,OAAO,aAAa,UAAa,MAAM,aAAc,QAAO,WAAW,OAAO;AAAA,QACpF;AAAA,MACF,CAAC;AACD,UAAI,CAAC,KAAM,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,iBAAiB,CAAC;AAEnE,YAAM,qBAAqB;AAAA,QACzB,YAAY;AAAA,QACZ,UAAU,EAAE,KAAK;AAAA,QACjB,UAAU,OAAO,KAAK,EAAE;AAAA,QACxB,gBAAgB;AAAA,QAChB,UAAU,KAAK,WAAW,OAAO,KAAK,QAAQ,IAAI;AAAA,QAClD,QAAQ;AAAA,MACV,CAAC;AAAA,IACH,CAAC,GAAG;AAAA,MACF,aAAa;AAAA,MACb,OAAO,eAAe,2BAA2B;AAAA,IACnD,CAAC;AACD,UAAM,cAAc;AACpB,QAAI,CAAC,YAAa,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,iBAAiB,CAAC;AAE1E,UAAM,oBAAoB;AAAA,MACxB,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa;AAAA,QACX,IAAI,OAAO,YAAY,EAAE;AAAA,QACzB,gBAAgB;AAAA,QAChB,UAAU,YAAY,WAAW,OAAO,YAAY,QAAQ,IAAI;AAAA,MAClE;AAAA,MACA,QAAQ;AAAA,MACR,SAAS;AAAA,IACX,CAAC;AAED,WAAO;AAAA,EACT;AAAA,EACA,cAAc,OAAO,QAAQ,QAAQ,QAAQ;AAC3C,UAAM,KAAK,iBAAiB,KAAK,IAAI;AACrC,UAAM,SAAS,MAAM,wBAAwB,IAAI;AAAA,MAC/C,UAAU,EAAE,KAAK;AAAA,MACjB,UAAU,OAAO,OAAO,EAAE;AAAA,MAC1B,UAAU,OAAO,WAAW,OAAO,OAAO,QAAQ,IAAI;AAAA,IACxD,CAAC;AACD,WAAO,cAAc,QAAQ,MAAM;AAAA,EACrC;AAAA,EACA,UAAU,OAAO,EAAE,QAAQ,WAAW,IAAI,MAAM;AAC9C,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,UAAM,kBAAkB,UAAU;AAClC,UAAM,SAAS,iBAAiB;AAChC,UAAM,aAAa,iBAAiB,QAAQ;AAC5C,UAAM,KAAK,iBAAiB,KAAK,IAAI;AACrC,UAAM,YAAY,MAAM,qBAAqB,IAAI,OAAO,OAAO,EAAE,CAAC;AAClE,UAAM,SAAS,MAAM,wBAAwB,IAAI;AAAA,MAC/C,UAAU,EAAE,KAAK;AAAA,MACjB,UAAU,OAAO,OAAO,EAAE;AAAA,MAC1B,UAAU,OAAO,WAAW,OAAO,OAAO,QAAQ,IAAI;AAAA,IACxD,CAAC;AACD,UAAM,iBAAiB,qBAAqB,QAAQ,WAAW,MAAM;AACrE,UAAM,QAAQ,eAAe;AAC7B,UAAM,UAAU,aAAa,UAAU,MAAM,OAAkC,CAAC,QAAQ,UAAU,CAAC;AACnG,UAAM,aAAa,uBAAuB,QAAQ,QAAQ,MAAM;AAChE,eAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,UAAU,GAAG;AACpD,cAAQ,MAAM,GAAG,EAAE,IAAI;AAAA,IACzB;AACA,WAAO;AAAA,MACL,aAAa,UAAU,2BAA2B,aAAa;AAAA,MAC/D,cAAc;AAAA,MACd,YAAY,OAAO,OAAO,EAAE;AAAA,MAC5B,UAAU,OAAO,WAAW,OAAO,OAAO,QAAQ,IAAI;AAAA,MACtD;AAAA,MACA,gBAAgB,UAAU;AAAA,MAC1B,eAAe;AAAA,MACf,SAAS;AAAA,QACP,MAAM;AAAA,UACJ,QAAQ;AAAA,UACR,OAAO,eAAe;AAAA,QACxB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EACA,MAAM,OAAO,EAAE,OAAO,UAAU,IAAI,MAAM;AACxC,UAAM,OAAO,mBAAoC,QAAQ;AACzD,UAAM,SAAS,MAAM;AACrB,UAAM,QAAQ,MAAM;AACpB,QAAI,CAAC,OAAQ;AACb,UAAM,oBAAoB,yBAAyB,SAAS,gBAAgB,QAAQ;AACpF,UAAM,cAAc,OAAO,KAAK,iBAAiB,EAAE,SAC/C,oBACA,4BAA4B,QAAQ,SAAS,IAAI;AACrD,UAAM,KAAK,iBAAiB,GAAG;AAC/B,UAAM,KAAM,IAAI,UAAU,QAAQ,YAAY;AAC9C,QAAI,UAAuB;AAC3B,UAAM,gBAAgB,IAAI,CAAC,YAAY;AACrC,YAAM,8BAA8B,UAAU;AAAA,QAC5C,WAAW;AAAA,QACX;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AACD,gBAAU,MAAM,GAAG,gBAAgB;AAAA,QACjC,QAAQ;AAAA,QACR,OAAO,EAAE,IAAI,OAAO,IAAI,WAAW,KAAK;AAAA,QACxC,OAAO,CAAC,WAAW;AACjB,cAAI,UAAU,YAAa,QAAO,OAAO,OAAO;AAChD,cAAI,cAAc,YAAa,QAAO,WAAW,OAAO;AAAA,QAC1D;AAAA,MACF,CAAC;AACD,YAAM,eAAe,cAAc,eAAe,OAAO,aAAa,OAAO;AAC7E,UAAI,WAAW,cAAc;AAC3B,cAAM,gBAAgB,IAAI,OAAO,IAAI,OAAO,IAAI;AAAA,MAClD;AACA,YAAM,QAAQ,yBAAyB,OAAO,QAAQ,OAAO,MAAM;AACnE,UAAI,OAAO,KAAK,KAAK,EAAE,QAAQ;AAC7B,cAAM,qBAAqB;AAAA,UACzB,YAAY;AAAA,UACZ,UAAU,EAAE,KAAK;AAAA,UACjB,UAAU,OAAO;AAAA,UACjB,gBAAgB;AAAA,UAChB,UAAU,OAAO;AAAA,UACjB,QAAQ;AAAA,UACR,QAAQ;AAAA,QACV,CAAC;AAAA,MACH;AAAA,IACF,CAAC,GAAG,EAAE,aAAa,MAAM,OAAO,yBAAyB,CAAC;AAC1D,UAAM,wBAAwB;AAAA,MAC5B,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa;AAAA,QACX,IAAI,OAAO;AAAA,QACX,gBAAgB;AAAA,QAChB,UAAU,OAAO,YAAY;AAAA,MAC/B;AAAA,MACA,QAAQ;AAAA,MACR,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AAAA,EACA,MAAM,CAAC,EAAE,OAAO,KAAK,SAAS,MAAM,kBAAkB,QAAQ,OAAO,gBAAgB,KAAK,QAAQ,CAAC;AACrG;AAEA,MAAM,oBAA+G;AAAA,EACnH,IAAI;AAAA,EACJ,cAAc;AAAA,EACd,iBAAiB;AAAA,EACjB,iBAAiB,CAAC,WAAW,oBAAoB,UAAU,MAAM;AAAA,EACjE,MAAM,QAAQ,OAAO,KAAK;AACxB,UAAM,KAAK,UAAU,OAAO,kBAAkB;AAC9C,UAAM,KAAK,iBAAiB,GAAG;AAC/B,UAAM,QAAQ,MAAM,kBAAkB,GAAG;AACzC,UAAM,WAAW,MAAM,sBAAsB,IAAI,MAAM,sBAAsB,IAAI,KAAK,GAAG,CAAC,GAAG,EAAE,UAAU,MAAM,eAAe,gBAAgB,KAAK,CAAC;AACpJ,QAAI,CAAC,SAAU,QAAO,CAAC;AACvB,UAAM,mBAAmB,wBAAwB,GAAG;AACpD,QAAI,kBAAkB;AACpB,YAAM,eAAe,kBAAkB,SAAS,QAAQ,KAAK;AAC7D,UAAI,CAAC,gBAAgB,iBAAiB,iBAAkB,QAAO,CAAC;AAAA,IAClE;AACA,UAAM,OAAO,MAAM,qBAAqB,IAAI,EAAE;AAC9C,UAAM,SAAS,MAAM,wBAAwB,IAAI;AAAA,MAC/C,UAAU,EAAE,KAAK;AAAA,MACjB,UAAU;AAAA,MACV,UAAU,SAAS,WAAW,OAAO,SAAS,QAAQ,IAAI;AAAA,IAC5D,CAAC;AACD,WAAO,EAAE,QAAQ,qBAAqB,UAAU,MAAM,MAAM,EAAE;AAAA,EAChE;AAAA,EACA,MAAM,QAAQ,OAAO,KAAK;AACxB,UAAM,KAAK,UAAU,OAAO,kBAAkB;AAC9C,UAAM,KAAK,iBAAiB,GAAG;AAC/B,UAAM,QAAQ,MAAM,kBAAkB,GAAG;AACzC,UAAM,OAAO,MAAM,sBAAsB,IAAI,MAAM,sBAAsB,IAAI,KAAK,GAAG,CAAC,GAAG,EAAE,UAAU,MAAM,eAAe,gBAAgB,KAAK,CAAC;AAChJ,QAAI,CAAC,KAAM,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,iBAAiB,CAAC;AACnE,4BAAwB,wBAAwB,GAAG,GAAG,KAAK,QAAQ;AAEnE,UAAM,KAAM,IAAI,UAAU,QAAQ,YAAY;AAC9C,UAAM,eAAgB,IAAiC,aAAa;AACpE,QAAI,UAAuB;AAC3B,UAAM,gBAAgB,IAAI,CAAC,YAAY;AACrC,UAAI,cAAc;AAChB,cAAM,8BAA8B,UAAU;AAAA,UAC5C,WAAW;AAAA,UACX;AAAA,UACA;AAAA,UACA,UAAU;AAAA,QACZ,CAAC;AAAA,MACH;AACA,YAAM,iCAAiC,IAAI,CAAC,EAAE,CAAC;AAC/C,YAAM,oBAAoB,MAAM,GAAG,MAAM,UAAU,EAAE,MAAM,WAAW,KAAK,CAAC;AAC5E,UAAI,oBAAoB,EAAG,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,0BAA0B,CAAC;AAC5F,YAAM,GAAG,aAAa,SAAS,EAAE,MAAM,GAAG,CAAC;AAC3C,gBAAU,MAAM,GAAG,gBAAgB;AAAA,QACjC,QAAQ;AAAA,QACR,OAAO,sBAAsB,IAAI,KAAK;AAAA,QACtC,MAAM;AAAA,MACR,CAAC;AACD,UAAI,CAAC,QAAS,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,iBAAiB,CAAC;AAAA,IACxE,CAAC,GAAG;AAAA,MACF,aAAa;AAAA,MACb,OAAO,eAAe,2BAA2B;AAAA,IACnD,CAAC;AACD,UAAM,cAAc;AACpB,QAAI,CAAC,YAAa,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,iBAAiB,CAAC;AAE1E,UAAM,oBAAoB;AAAA,MACxB,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa;AAAA,QACX;AAAA,QACA,gBAAgB;AAAA,QAChB,UAAU,YAAY,WAAW,OAAO,YAAY,QAAQ,IAAI;AAAA,MAClE;AAAA,MACA,QAAQ;AAAA,MACR,SAAS;AAAA,IACX,CAAC;AAED,WAAO;AAAA,EACT;AAAA,EACA,UAAU,OAAO,EAAE,WAAW,MAAM,MAAM;AACxC,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,UAAM,kBAAkB,UAAU;AAClC,UAAM,SAAS,iBAAiB;AAChC,UAAM,aAAa,iBAAiB,QAAQ;AAC5C,UAAM,KAAK,UAAU,OAAO,kBAAkB;AAC9C,WAAO;AAAA,MACL,aAAa,UAAU,2BAA2B,aAAa;AAAA,MAC/D,cAAc;AAAA,MACd,YAAY;AAAA,MACZ,UAAU,QAAQ,YAAY;AAAA,MAC9B,gBAAgB,UAAU;AAAA,MAC1B,SAAS;AAAA,QACP,MAAM;AAAA,UACJ,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EACA,MAAM,OAAO,EAAE,OAAO,UAAU,IAAI,MAAM;AACxC,UAAM,SAAS,mBAAoC,QAAQ,GAAG;AAC9D,QAAI,CAAC,OAAQ;AACb,UAAM,KAAK,iBAAiB,GAAG;AAC/B,UAAM,KAAM,IAAI,UAAU,QAAQ,YAAY;AAC9C,QAAI,OAAoB;AACxB,UAAM,gBAAgB,IAAI,CAAC,YAAY;AACrC,YAAM,8BAA8B,UAAU;AAAA,QAC5C,WAAW;AAAA,QACX;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AACD,aAAO,MAAM,sBAAsB,IAAI,MAAM,EAAE,IAAI,OAAO,GAAG,GAAG,CAAC,GAAG,EAAE,UAAU,MAAM,gBAAgB,KAAK,CAAC;AAC5G,UAAI,MAAM;AACR,aAAK,YAAY;AACjB,aAAK,OAAO,OAAO;AACnB,aAAK,WAAW,OAAO;AACvB,cAAM,GAAG,MAAM;AAAA,MACjB,OAAO;AACL,eAAO,MAAM,GAAG,gBAAgB;AAAA,UAC9B,QAAQ;AAAA,UACR,MAAM;AAAA,YACJ,IAAI,OAAO;AAAA,YACX,MAAM,OAAO;AAAA,YACb,UAAU,OAAO;AAAA,UACnB;AAAA,QACF,CAAC;AAAA,MACH;AACA,YAAM,gBAAgB,IAAI,OAAO,IAAI,OAAO,IAAI;AAChD,YAAM,QAAQ,yBAAyB,OAAO,QAAQ,MAAS;AAC/D,UAAI,OAAO,KAAK,KAAK,EAAE,QAAQ;AAC7B,cAAM,qBAAqB;AAAA,UACzB,YAAY;AAAA,UACZ,UAAU,EAAE,KAAK;AAAA,UACjB,UAAU,OAAO;AAAA,UACjB,gBAAgB;AAAA,UAChB,UAAU,OAAO,YAAY;AAAA,UAC7B,QAAQ;AAAA,UACR,QAAQ;AAAA,QACV,CAAC;AAAA,MACH;AAAA,IACF,CAAC,GAAG,EAAE,aAAa,MAAM,OAAO,yBAAyB,CAAC;AAC1D,QAAI,CAAC,KAAM,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,6CAA6C,CAAC;AAC/F,UAAM,wBAAwB;AAAA,MAC5B,YAAY;AAAA,MACZ,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,aAAa;AAAA,QACX,IAAI,OAAO;AAAA,QACX,gBAAgB;AAAA,QAChB,UAAU,OAAO,YAAY;AAAA,MAC/B;AAAA,MACA,QAAQ;AAAA,MACR,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AAAA,EACA,MAAM,CAAC,EAAE,OAAO,KAAK,SAAS,MAAM,kBAAkB,QAAQ,OAAO,gBAAgB,KAAK,QAAQ,CAAC;AACrG;AAIA,SAAS,0BAA0B,MAA4C;AAC7E,SAAO,KACJ,IAAI,CAAC,SAAS;AAAA,IACb,GAAG;AAAA,IACH,UAAU,IAAI,WAAW,CAAC,GAAG,IAAI,QAAQ,EAAE,KAAK,CAAC,MAAM,UAAU,KAAK,cAAc,KAAK,CAAC,IAAI;AAAA,IAC9F,eAAe,IAAI,gBAAgB,CAAC,GAAG,IAAI,aAAa,EAAE,KAAK,CAAC,MAAM,UAAU,KAAK,cAAc,KAAK,CAAC,IAAI;AAAA,EAC/G,EAAE,EACD,KAAK,CAAC,MAAM,UAAU;AACrB,UAAM,cAAc,KAAK,SAAS,cAAc,MAAM,QAAQ;AAC9D,QAAI,gBAAgB,EAAG,QAAO;AAC9B,YAAQ,KAAK,MAAM,IAAI,cAAc,MAAM,MAAM,EAAE;AAAA,EACrD,CAAC;AACL;AAEA,SAAS,4BAA4B,UAA8C;AACjF,SAAO;AAAA,IACL,GAAG;AAAA,IACH,MAAM,0BAA0B,SAAS,IAAI;AAAA,EAC/C;AACF;AAEA,SAAS,wBAAwB,UAA4C;AAC3E,SAAO;AAAA,IACL,MAAM,SAAS;AAAA,IACf,UAAU,SAAS;AAAA,IACnB,GAAI,SAAS,UAAU,OAAO,KAAK,SAAS,MAAM,EAAE,SAAS,EAAE,QAAQ,SAAS,OAAO,IAAI,CAAC;AAAA,EAC9F;AACF;AAEA,SAAS,4BACP,QACA,OACyB;AACzB,MAAI,CAAC,OAAQ,QAAO,CAAC;AACrB,MAAI,CAAC,MAAO,QAAO,EAAE,IAAI,OAAO,IAAI,MAAM,OAAO,MAAM,UAAU,OAAO,SAAS;AACjF,QAAM,QAAiC,EAAE,IAAI,MAAM,GAAG;AACtD,MAAI,OAAO,SAAS,MAAM,KAAM,OAAM,OAAO,MAAM;AACnD,MAAI,OAAO,aAAa,MAAM,SAAU,OAAM,WAAW,MAAM;AAC/D,SAAO;AACT;AAEA,eAAe,oBACb,aACA,QACe;AACf,QAAM,EAAE,WAAW,KAAK,SAAS,IAAI;AACrC,QAAM,KAAK,wCAAwC,GAAG;AACtD,QAAM,4BAA4B,KAAK,mBAAmB;AAE1D,QAAM,cAAc,mBAAoC,QAAQ;AAChE,QAAM,SAAS,aAAa,UAAU;AACtC,QAAM,QAAQ,aAAa,SAAS;AACpC,QAAM,SAAS,SAAS,cAAc,QAAQ,MAAM,OAAO,MAAM;AACjE,MAAI,CAAC,QAAQ;AACX,UAAM,4BAA4B,MAAM,EAAE,UAAU,uBAAuB,CAAC;AAC5E;AAAA,EACF;AAEA,QAAM,cAAc,MAAM;AAAA,IACxB;AAAA,IACA;AAAA,IACA,EAAE,IAAI,QAAQ,WAAW,KAAK;AAAA,IAC9B,CAAC;AAAA,IACD,EAAE,UAAU,MAAM,gBAAgB,KAAK;AAAA,EACzC;AACA,QAAM,cAAc,IAAI,gBAAgB,OACpC,OACA,IAAI,UAAU,QAAQ,aAAa;AACvC,MAAI,eAAe,aAAa;AAC9B,UAAM,+BAA+B;AAAA,MACnC;AAAA,MACA;AAAA,MACA,kBAAkB;AAAA,MAClB,aAAa,IAAI,MAAM;AAAA,MACvB,UAAU,IAAI,MAAM,YAAY;AAAA,MAChC,gBAAgB,IAAI,MAAM,SAAS;AAAA,MACnC,cAAc;AAAA,IAChB,CAAC;AACD,UAAM,yCAAyC;AAAA,MAC7C;AAAA,MACA;AAAA,MACA,kBAAkB;AAAA,MAClB,aAAa,IAAI,MAAM;AAAA,MACvB,UAAU,IAAI,MAAM,YAAY;AAAA,MAChC,gBAAgB,IAAI,MAAM,SAAS;AAAA,MACnC,cAAc;AAAA,IAChB,CAAC;AAAA,EACH;AAEA,MAAI,kBAA2C;AAC/C,MAAI,aAAa;AACf,UAAM,kBAAkB,YAAY,WAAW,OAAO,YAAY,QAAQ,IAAI;AAC9E,UAAM,CAAC,MAAM,MAAM,IAAI,MAAM,QAAQ,IAAI;AAAA,MACvC,qBAAqB,IAAI,MAAM;AAAA,MAC/B,wBAAwB,IAAI;AAAA,QAC1B,UAAU,EAAE,KAAK;AAAA,QACjB,UAAU;AAAA,QACV,UAAU;AAAA,MACZ,CAAC;AAAA,IACH,CAAC;AACD,sBAAkB,qBAAqB,aAAa,MAAM,MAAM,EAAE;AAAA,EACpE;AAEA,QAAM,kBAAkB,cAAc,SAClC,gBAAgB,WAAW,QAAQ,gBAAgB,WAAW,QAAQ,OACtE,gBAAgB,WAAW,OAAO;AACtC,QAAM,oBAAoB,yBAAyB,OAAO,OAAO,QAAQ;AACzE,QAAM,cAAc,OAAO,KAAK,iBAAiB,EAAE,SAC/C,oBACA,4BAA4B,QAAQ,KAAK;AAC7C,QAAM,cAAc,gBAAgB,YAC/B,cAAc,eACd,QAAQ,aAAa,OAAO;AACjC,MAAI,aAAa;AACf,UAAM,+BAA+B,GAAG;AAAA,EAC1C;AAEA,MAAI,gBAAgB,YAAY,CAAC,aAAa;AAC5C,QAAI,CAAC,mBAAmB,CAAC,iBAAiB;AACxC,YAAM,4BAA4B,iBAAiB,eAAe;AAAA,IACpE,OAAO;AACL,YAAM;AAAA,QACJ,wBAAwB,eAAe;AAAA,QACvC,wBAAwB,eAAe;AAAA,MACzC;AAAA,IACF;AAAA,EACF,OAAO;AACL,UAAM;AAAA,MACJ,kBAAkB,4BAA4B,eAAe,IAAI;AAAA,MACjE,kBAAkB,4BAA4B,eAAe,IAAI;AAAA,IACnE;AAAA,EACF;AAEA,QAAM,eAAe,cAAc,SAC/B,gBAAgB,WAAW,OAAO,SAClC,gBAAgB,WAAW,OAAO;AACtC,MAAI,gBAAgB,CAAC,eAAe,aAAa;AAC/C,UAAM,cAAc,IAAI,MAAM;AAC9B,QAAI,CAAC,YAAa,OAAM,UAAU;AAClC,UAAM,WAAW,MAAM,YAAY,yBAAyB,IAAI,aAAa;AAAA,MAC3E,UAAU,IAAI,MAAM,YAAY;AAAA,MAChC,gBAAgB,IAAI,MAAM,SAAS;AAAA,IACrC,CAAC;AACD,QAAI,CAAC,SAAS,aAAc,yBAAwB,IAAI,MAAM,YAAY,MAAM,aAAa,QAAQ;AAAA,EACvG;AACA,QAAM,eAAe;AAAA,IACnB,gBACK,aAAa,KAAK,SAAS,MAC1B,gBAAgB,YAAY;AAAA,EACpC;AACA,MAAI,CAAC,gBAAgB,CAAC,gBAAgB,CAAC,YAAa;AAEpD,QAAM,4BAA4B,KAAK,iBAAiB;AACxD,aAAW,OAAO,aAAa,MAAM;AACnC,UAAM,uBAAuB;AAAA,MAC3B;AAAA,MACA;AAAA,MACA,kBAAkB;AAAA,MAClB,aAAa,IAAI,MAAM;AAAA,MACvB,UAAU,IAAI;AAAA,MACd,gBAAgB;AAAA,MAChB,UAAU,IAAI;AAAA,MACd,cAAc,IAAI;AAAA,MAClB,eAAe,IAAI;AAAA,IACrB,CAAC;AAAA,EACH;AACF;AAEA,eAAe,8BACb,aACA,QACe;AACf,QAAM,KAAK,OAAO,IAAI,mBACjB,OAAO,IAAI,UAAU,QAAQ,IAAI;AACtC,QAAM,cAAc,OAAO,IAAI,kBAC3B,SACA,EAAE,GAAG,QAAQ,KAAK,EAAE,GAAG,OAAO,KAAK,iBAAiB,GAAG,EAAE;AAC7D,QAAM,oBAAoB,WAAW;AACrC,QAAM,sCAAsC,aAAa,EAAE;AAC3D,QAAM,oBAAoB,aAAa,WAAW;AACpD;AAEA,eAAe,oBACb,QACe;AACf,QAAM,cAAc,mBAAoC,OAAO,QAAQ;AACvE,QAAM,SAAS,aAAa,UAAU;AACtC,QAAM,QAAQ,aAAa,SAAS;AACpC,QAAM,eAAe,OAAO,SAAS,cAAc,QAAQ,MAAM,OAAO,MAAM;AAC9E,QAAM,KAAK,wCAAwC,OAAO,GAAG;AAC7D,QAAM,6BAA6B,IAAI,OAAO,KAAK;AAAA,IACjD,eAAe,eAAe,CAAC,YAAY,IAAI,CAAC;AAAA,IAChD,iBAAiB;AAAA,MACf,GAAI,QAAQ,WAAW,CAAC,OAAO,QAAQ,IAAI,CAAC;AAAA,MAC5C,GAAI,OAAO,WAAW,CAAC,MAAM,QAAQ,IAAI,CAAC;AAAA,MAC1C,GAAI,OAAO,SAAS,WAAW,CAAC,OAAO,SAAS,QAAQ,IAAI,CAAC;AAAA,IAC/D;AAAA,EACF,CAAC;AACH;AAEA,gBAAgB,iBAAiB;AACjC,gBAAgB,iBAAiB;AACjC,gBAAgB,iBAAiB;AAEjC,SAAS,cAAc,MAAY,QAAyD;AAC1F,QAAM,UAA0B;AAAA,IAC9B,MAAM,OAAO,KAAK,QAAQ,EAAE;AAAA,IAC5B,UAAU,OAAO,KAAK,QAAQ;AAAA,EAChC;AACA,MAAI,UAAU,OAAO,KAAK,MAAM,EAAE,OAAQ,SAAQ,SAAS;AAC3D,SAAO;AACT;AAEA,SAAS,qBACP,MACA,OAA0B,CAAC,GAC3B,QACe;AACf,SAAO;AAAA,IACL,MAAM,cAAc,MAAM,MAAM;AAAA,IAChC,MAAM;AAAA,MACJ,IAAI,OAAO,KAAK,EAAE;AAAA,MAClB,MAAM,OAAO,KAAK,QAAQ,EAAE;AAAA,MAC5B,UAAU,OAAO,KAAK,QAAQ;AAAA,MAC9B;AAAA,MACA,GAAI,UAAU,OAAO,KAAK,MAAM,EAAE,SAAS,EAAE,OAAO,IAAI,CAAC;AAAA,IAC3D;AAAA,EACF;AACF;AAEA,eAAe,qBAAqB,IAAmB,QAA4C;AACjG,QAAM,UAAU,MAAM,mBAAmB,IAAI,SAAS,EAAE,MAAM,QAA2B,WAAW,KAAK,GAAG,CAAC,GAAG,EAAE,UAAU,MAAM,gBAAgB,KAAK,CAAC;AACxJ,SAAO,QAAQ,IAAI,CAAC,WAAW;AAAA,IAC7B,IAAI,MAAM,KAAK,OAAO,MAAM,EAAE,IAAI;AAAA,IAClC,UAAU,OAAO,MAAM,QAAQ;AAAA,IAC/B,UAAU,MAAM,QAAQ,MAAM,YAAY,IAAI,CAAC,GAAG,MAAM,YAAY,IAAI;AAAA,IACxE,cAAc,QAAQ,MAAM,YAAY;AAAA,IACxC,eAAe,MAAM,QAAQ,MAAM,iBAAiB,IAAI,CAAC,GAAG,MAAM,iBAAiB,IAAI;AAAA,EACzF,EAAE;AACJ;AAEA,eAAe,gBAAgB,IAAmB,QAAgB,MAAyB;AACzF,QAAM,iCAAiC,IAAI,CAAC,MAAM,CAAC;AACnD,QAAM,GAAG,aAAa,SAAS,EAAE,MAAM,OAA0B,CAAC;AAClE,MAAI,CAAC,KAAK,QAAQ;AAChB,UAAM,GAAG,MAAM;AACf;AAAA,EACF;AACA,QAAM,UAAU,GAAG,aAAa,MAAM,MAAM;AAC5C,aAAW,OAAO,MAAM;AACtB,UAAM,SAAS,GAAG,OAAO,SAAS;AAAA,MAChC,IAAI,IAAI,MAAM;AAAA,MACd,MAAM;AAAA,MACN,UAAU,IAAI;AAAA,MACd,cAAc,IAAI,YAAY;AAAA,MAC9B,cAAc,IAAI;AAAA,MAClB,mBAAmB,IAAI,iBAAiB;AAAA,MACxC,WAAW,oBAAI,KAAK;AAAA,IACtB,CAAC;AACD,OAAG,QAAQ,MAAM;AAAA,EACnB;AACA,QAAM,GAAG,MAAM;AACjB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { resolveQueryIndexSourceTableMetadata } from "./subscriber-scope.js";
|
|
2
3
|
import { resolveEntityTableName as resolveFallbackEntityTableName } from "@open-mercato/shared/lib/query/engine";
|
|
3
4
|
import { resolveTenantEncryptionService } from "@open-mercato/shared/lib/encryption/customFieldValues";
|
|
@@ -9,6 +10,8 @@ import {
|
|
|
9
10
|
} from "@open-mercato/shared/lib/crud/custom-field-definition-index";
|
|
10
11
|
import { sql } from "kysely";
|
|
11
12
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
13
|
+
import { recordIndexerError } from "@open-mercato/shared/lib/indexers/error-log";
|
|
14
|
+
import { getTelemetryRuntime } from "@open-mercato/shared/lib/telemetry/runtime";
|
|
12
15
|
import { resolveSearchConfig } from "@open-mercato/shared/lib/search/config";
|
|
13
16
|
import { replaceSearchTokensForRecord, deleteSearchTokensForRecord } from "./search-tokens.js";
|
|
14
17
|
import { attachAggregateSearchField, rebuildAggregateSearchField } from "./document.js";
|
|
@@ -17,6 +20,13 @@ function resolveEntityTableName(em, entity) {
|
|
|
17
20
|
return resolveQueryIndexSourceTableMetadata(em, entity)?.table ?? resolveFallbackEntityTableName(em, entity);
|
|
18
21
|
}
|
|
19
22
|
const logger = createLogger("query_index").child({ component: "indexer" });
|
|
23
|
+
function reportTranslationsFailure(error, code) {
|
|
24
|
+
try {
|
|
25
|
+
getTelemetryRuntime()?.reportError(error, { module: "query_index", code });
|
|
26
|
+
} catch (telemetryError) {
|
|
27
|
+
logger.warn("Failed to report index translations error", { err: telemetryError });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
20
30
|
async function buildIndexDoc(em, params) {
|
|
21
31
|
const db = em.getKysely();
|
|
22
32
|
const baseTable = resolveEntityTableName(em, params.entityType);
|
|
@@ -129,6 +139,8 @@ async function buildIndexDoc(em, params) {
|
|
|
129
139
|
const fieldKey = key.slice(3);
|
|
130
140
|
doc[key] = multiKeys.has(fieldKey) || arr.length > 1 ? arr : arr[0];
|
|
131
141
|
}
|
|
142
|
+
const translationsSavepoint = db.isTransaction ? `om_translations_${randomUUID()}` : null;
|
|
143
|
+
if (translationsSavepoint) await sql`SAVEPOINT ${sql.id(translationsSavepoint)}`.execute(db);
|
|
132
144
|
try {
|
|
133
145
|
const translationRow = await db.selectFrom("entity_translations").select(["translations"]).where("entity_type", "=", params.entityType).where("entity_id", "=", String(params.recordId)).where(sql`tenant_id is not distinct from ${params.tenantId ?? null}`).where(sql`organization_id is not distinct from ${params.organizationId ?? null}`).executeTakeFirst();
|
|
134
146
|
if (translationRow?.translations && typeof translationRow.translations === "object") {
|
|
@@ -141,7 +153,33 @@ async function buildIndexDoc(em, params) {
|
|
|
141
153
|
}
|
|
142
154
|
}
|
|
143
155
|
}
|
|
144
|
-
} catch {
|
|
156
|
+
} catch (translationsError) {
|
|
157
|
+
if (translationsSavepoint) {
|
|
158
|
+
await sql`ROLLBACK TO SAVEPOINT ${sql.id(translationsSavepoint)}`.execute(db);
|
|
159
|
+
}
|
|
160
|
+
reportTranslationsFailure(translationsError, "query_index.translations_read_failed");
|
|
161
|
+
await recordIndexerError(
|
|
162
|
+
{ db },
|
|
163
|
+
{
|
|
164
|
+
source: "query_index",
|
|
165
|
+
handler: "query_index:build-index-doc:translations",
|
|
166
|
+
error: translationsError,
|
|
167
|
+
entityType: params.entityType,
|
|
168
|
+
recordId: params.recordId,
|
|
169
|
+
tenantId: params.tenantId ?? null,
|
|
170
|
+
organizationId: params.organizationId ?? null
|
|
171
|
+
}
|
|
172
|
+
).catch((loggingError) => reportTranslationsFailure(loggingError, "query_index.translations_log_failed"));
|
|
173
|
+
} finally {
|
|
174
|
+
if (translationsSavepoint) {
|
|
175
|
+
try {
|
|
176
|
+
await sql`RELEASE SAVEPOINT ${sql.id(translationsSavepoint)}`.execute(db);
|
|
177
|
+
} catch (loggingError) {
|
|
178
|
+
await sql`ROLLBACK TO SAVEPOINT ${sql.id(translationsSavepoint)}`.execute(db);
|
|
179
|
+
await sql`RELEASE SAVEPOINT ${sql.id(translationsSavepoint)}`.execute(db);
|
|
180
|
+
reportTranslationsFailure(loggingError, "query_index.translations_log_failed");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
145
183
|
}
|
|
146
184
|
await applyIndexDocEnrichers(db, params.entityType, [{
|
|
147
185
|
recordId: String(params.recordId),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/query_index/lib/indexer.ts"],
|
|
4
|
-
"sourcesContent": ["import { resolveQueryIndexSourceTableMetadata } from './subscriber-scope'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { resolveEntityTableName as resolveFallbackEntityTableName } from '@open-mercato/shared/lib/query/engine'\nimport { resolveTenantEncryptionService } from '@open-mercato/shared/lib/encryption/customFieldValues'\nimport { decryptIndexDocForSearch, encryptIndexDocForStorage } from '@open-mercato/shared/lib/encryption/indexDoc'\nimport {\n buildCustomFieldDefinitionIndexFromRows,\n normalizeDefinitionKey,\n selectDefinitionForRecord,\n type CustomFieldDefinitionRow,\n} from '@open-mercato/shared/lib/crud/custom-field-definition-index'\nimport { type Kysely, type Transaction, sql } from 'kysely'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { resolveSearchConfig } from '@open-mercato/shared/lib/search/config'\nimport { replaceSearchTokensForRecord, deleteSearchTokensForRecord } from './search-tokens'\nimport { attachAggregateSearchField, rebuildAggregateSearchField } from './document'\nimport { applyIndexDocEnrichers } from './doc-enrichers'\n\nfunction resolveEntityTableName(em: EntityManager, entity: string): string {\n return resolveQueryIndexSourceTableMetadata(em, entity)?.table ?? resolveFallbackEntityTableName(em, entity)\n}\n\nconst logger = createLogger('query_index').child({ component: 'indexer' })\n\ntype QueryIndexExecutor = Kysely<any> | Transaction<any>\n\ntype BuildDocParams = {\n entityType: string // '<module>:<entity>'\n recordId: string\n organizationId?: string | null\n tenantId?: string | null\n}\n\nexport async function buildIndexDoc(em: EntityManager, params: BuildDocParams): Promise<Record<string, any> | null> {\n const db = (em as any).getKysely()\n const baseTable = resolveEntityTableName(em, params.entityType)\n\n // Fetch base row\n const baseRow = await db\n .selectFrom(baseTable as any)\n .selectAll()\n .where('id' as any, '=', params.recordId)\n .executeTakeFirst() as Record<string, any> | undefined\n if (!baseRow) return null\n const docSources: Array<Record<string, any>> = []\n\n // Attach the core customer entity when indexing customer profiles so search tokens see the combined row\n let parentEntityRow: Record<string, any> | null = null\n if (params.entityType === 'customers:customer_person_profile' || params.entityType === 'customers:customer_company_profile') {\n const entityId = (baseRow as any).entity_id ?? (baseRow as any).entityId\n if (entityId) {\n const entityRow = await db\n .selectFrom('customer_entities' as any)\n .selectAll()\n .where('id' as any, '=', entityId)\n .executeTakeFirst() as Record<string, any> | undefined\n if (entityRow) {\n docSources.push(entityRow)\n parentEntityRow = entityRow\n }\n }\n }\n void parentEntityRow\n\n // Build base document (snake_case keys as in DB)\n let doc: Record<string, any> = {}\n docSources.push(baseRow)\n for (const source of docSources) {\n for (const [k, v] of Object.entries(source)) doc[k] = v\n }\n\n // Attach custom fields under flat keys 'cf:<key>'\n let cfQuery = db\n .selectFrom('custom_field_values' as any)\n .select([\n 'field_key' as any,\n 'value_text' as any,\n 'value_multiline' as any,\n 'value_int' as any,\n 'value_float' as any,\n 'value_bool' as any,\n ])\n .where('entity_id' as any, '=', params.entityType)\n .where('record_id' as any, '=', String(params.recordId))\n\n if (params.organizationId != null) {\n cfQuery = cfQuery.where((eb: any) => eb.or([\n eb('organization_id' as any, '=', params.organizationId),\n eb('organization_id' as any, 'is', null),\n ]))\n } else {\n cfQuery = cfQuery.where('organization_id' as any, 'is', null)\n }\n\n if (params.tenantId != null) {\n cfQuery = cfQuery.where((eb: any) => eb.or([\n eb('tenant_id' as any, '=', params.tenantId),\n eb('tenant_id' as any, 'is', null),\n ]))\n } else {\n cfQuery = cfQuery.where('tenant_id' as any, 'is', null)\n }\n\n const cfRows = await cfQuery.execute() as Array<Record<string, any>>\n\n const cfMap: Record<string, any[]> = {}\n for (const r of cfRows) {\n const key = String(r.field_key)\n const cfKey = `cf:${key}`\n const val = r.value_bool ?? r.value_int ?? r.value_float ?? r.value_text ?? r.value_multiline ?? null\n if (!cfMap[cfKey]) cfMap[cfKey] = []\n cfMap[cfKey].push(val)\n }\n\n const multiKeys = new Set<string>()\n const fieldKeys = Object.keys(cfMap).map((key) => key.slice(3))\n if (fieldKeys.length > 0) {\n let defQuery = db\n .selectFrom('custom_field_defs' as any)\n .select([\n 'key' as any,\n 'kind' as any,\n 'config_json' as any,\n 'organization_id' as any,\n 'tenant_id' as any,\n 'deleted_at' as any,\n 'updated_at' as any,\n ])\n .where('entity_id' as any, '=', params.entityType)\n .where('key' as any, 'in', fieldKeys)\n .where('is_active' as any, '=', true)\n .where('deleted_at' as any, 'is', null)\n\n if (params.organizationId != null) {\n defQuery = defQuery.where((eb: any) => eb.or([\n eb('organization_id' as any, '=', params.organizationId),\n eb('organization_id' as any, 'is', null),\n ]))\n } else {\n defQuery = defQuery.where('organization_id' as any, 'is', null)\n }\n\n if (params.tenantId != null) {\n defQuery = defQuery.where((eb: any) => eb.or([\n eb('tenant_id' as any, '=', params.tenantId),\n eb('tenant_id' as any, 'is', null),\n ]))\n } else {\n defQuery = defQuery.where('tenant_id' as any, 'is', null)\n }\n\n const defRows = await defQuery.execute() as Array<Record<string, any>>\n const definitionIndex = buildCustomFieldDefinitionIndexFromRows(\n defRows.map((row): CustomFieldDefinitionRow => ({\n key: String(row.key ?? ''),\n entityId: params.entityType,\n kind: typeof row.kind === 'string' ? row.kind : null,\n configJson: row.config_json,\n organizationId: row.organization_id ?? null,\n tenantId: row.tenant_id ?? null,\n deletedAt: row.deleted_at ?? null,\n updatedAt: row.updated_at ?? null,\n })),\n { organizationIds: params.organizationId ? [params.organizationId] : [] },\n )\n for (const key of fieldKeys) {\n const definitions = definitionIndex.get(normalizeDefinitionKey(key)) ?? []\n const definition = selectDefinitionForRecord(\n definitions,\n params.organizationId ?? null,\n params.tenantId ?? null,\n )\n if (definition?.multi) multiKeys.add(key)\n }\n }\n\n for (const [key, arr] of Object.entries(cfMap)) {\n // Cardinality belongs to the field definition, not the current row count:\n // a multi field containing one value must still hydrate as an array.\n const fieldKey = key.slice(3)\n doc[key] = multiKeys.has(fieldKey) || arr.length > 1 ? arr : arr[0]\n }\n\n // Attach translations under flat keys 'l10n:{locale}:{field}'\n try {\n const translationRow = await db\n .selectFrom('entity_translations' as any)\n .select(['translations' as any])\n .where('entity_type' as any, '=', params.entityType)\n .where('entity_id' as any, '=', String(params.recordId))\n .where(sql`tenant_id is not distinct from ${params.tenantId ?? null}`)\n .where(sql`organization_id is not distinct from ${params.organizationId ?? null}`)\n .executeTakeFirst() as { translations: Record<string, Record<string, unknown>> | null } | undefined\n\n if (translationRow?.translations && typeof translationRow.translations === 'object') {\n for (const [locale, fields] of Object.entries(translationRow.translations)) {\n if (!fields || typeof fields !== 'object') continue\n for (const [field, value] of Object.entries(fields as Record<string, unknown>)) {\n if (typeof value === 'string' && value.length > 0) {\n doc[`l10n:${locale}:${field}`] = value\n }\n }\n }\n }\n } catch {}\n\n await applyIndexDocEnrichers(db, params.entityType, [{\n recordId: String(params.recordId),\n doc,\n tenantId: params.tenantId ?? null,\n organizationId: params.organizationId ?? null,\n }])\n\n // Kept outside the guard below: a failure while building the aggregate search field is a\n // bug in the aggregation or its configuration, and must surface instead of being mistaken\n // for an encryption failure and silently skipping encryption.\n doc = attachAggregateSearchField(doc, { entityType: params.entityType })\n\n try {\n const encryption = resolveTenantEncryptionService(em as any)\n doc = await encryptIndexDocForStorage(\n params.entityType,\n doc,\n { tenantId: params.tenantId ?? null, organizationId: params.organizationId ?? null },\n encryption,\n )\n } catch (error) {\n // `encryptIndexDocForStorage` already returns the document untouched when encryption is\n // absent or disabled, so a throw here is always a genuine encryption failure. Returning\n // `doc` would hand the caller the plaintext document to write into `entity_indexes`,\n // which must stay encrypted at rest; returning `null` would mean \"record gone\" and make\n // `upsertIndexRow` delete a healthy index row. Fail loudly instead: the projection keeps\n // its previous, correctly encrypted contents and the `query_index.upsert_one` subscriber\n // persists the error via `recordIndexerError`.\n logger.error('Index document encryption failed; refusing to return an unencrypted document', {\n entityType: params.entityType,\n recordId: params.recordId,\n tenantId: params.tenantId ?? null,\n organizationId: params.organizationId ?? null,\n err: error,\n })\n throw error\n }\n\n return doc\n}\n\nexport type UpsertIndexResult = {\n doc: Record<string, any> | null\n existed: boolean\n wasDeleted: boolean\n created: boolean\n revived: boolean\n}\n\nfunction scopeEntityIndexes<QB extends { where: (...args: any[]) => QB }>(\n q: QB,\n args: { entityType: string; recordId: string; organizationId?: string | null; tenantId?: string | null },\n): QB {\n let chain = q.where('entity_type' as any, '=', args.entityType)\n chain = chain.where('entity_id' as any, '=', String(args.recordId))\n chain = args.organizationId == null\n ? chain.where('organization_id' as any, 'is', null as any)\n : chain.where('organization_id' as any, '=', args.organizationId)\n chain = chain.where(sql`tenant_id is not distinct from ${args.tenantId ?? null}`)\n return chain\n}\n\nexport async function upsertIndexRow(\n em: EntityManager,\n args: { entityType: string; recordId: string; organizationId?: string | null; tenantId?: string | null; searchTokenDoc?: Record<string, unknown> | null; deferSearchTokens?: boolean; trx?: QueryIndexExecutor }\n): Promise<UpsertIndexResult> {\n const db = (em as any).getKysely()\n const executor = args.trx ?? db\n\n const existing = await scopeEntityIndexes(\n executor.selectFrom('entity_indexes' as any).select(['id' as any, 'deleted_at' as any]),\n args,\n ).executeTakeFirst() as { id: string; deleted_at: Date | null } | undefined\n\n const existed = !!existing\n const wasDeleted = !!existing && existing.deleted_at != null\n\n const doc = await buildIndexDoc(em, args)\n if (!doc) {\n // When the caller defers token work it owns the matching token cleanup; the\n // projection-row removal below stays synchronous so list reads converge immediately.\n if (!args.deferSearchTokens) {\n try {\n await reindexSearchTokensForRecord(em, {\n entityType: args.entityType,\n recordId: args.recordId,\n organizationId: args.organizationId ?? null,\n tenantId: args.tenantId ?? null,\n doc: null,\n trx: args.trx,\n })\n } catch (error) {\n if (args.trx) throw error\n }\n }\n if (existed) {\n await scopeEntityIndexes(\n executor.deleteFrom('entity_indexes' as any) as any,\n args,\n ).execute()\n }\n return { doc: null, existed, wasDeleted, created: false, revived: false }\n }\n\n const payload = {\n entity_type: args.entityType,\n entity_id: String(args.recordId),\n organization_id: args.organizationId ?? null,\n tenant_id: args.tenantId ?? null,\n doc: sql`${JSON.stringify(doc)}::jsonb`,\n index_version: 1,\n updated_at: sql`now()`,\n deleted_at: null,\n }\n\n // Prefer modern upsert keyed by coalesced org id when available; fallback to update-then-insert\n try {\n await executor\n .insertInto('entity_indexes' as any)\n .values({ ...payload, created_at: sql`now()` } as any)\n .onConflict((oc: any) => oc\n .columns(['entity_type', 'entity_id', 'organization_id_coalesced'])\n .doUpdateSet({\n tenant_id: args.tenantId ?? null,\n doc: sql`${JSON.stringify(doc)}::jsonb`,\n index_version: 1,\n updated_at: sql`now()`,\n deleted_at: null,\n } as any))\n .execute()\n } catch {\n // Fallback for schemas without organization_id_coalesced column/index\n const updated = await scopeEntityIndexes(\n executor.updateTable('entity_indexes' as any).set(payload as any) as any,\n args,\n ).executeTakeFirst() as { numUpdatedRows?: bigint | number } | undefined\n if (!updated || Number(updated.numUpdatedRows ?? 0) === 0) {\n try {\n await executor\n .insertInto('entity_indexes' as any)\n .values({ ...payload, created_at: sql`now()` } as any)\n .execute()\n } catch (error) {\n if (args.trx) throw error\n }\n }\n }\n\n const created = !existed\n const revived = existed && wasDeleted\n // The search-token rebuild (DELETE + chunked INSERT) is the heavy tail of indexing.\n // Callers that defer it (the upsert subscriber) run `reindexSearchTokensForRecord`\n // asynchronously after this projection update so write latency stays bounded.\n if (!args.deferSearchTokens) {\n try {\n await reindexSearchTokensForRecord(em, {\n entityType: args.entityType,\n recordId: args.recordId,\n organizationId: args.organizationId ?? null,\n tenantId: args.tenantId ?? null,\n doc,\n searchTokenDoc: args.searchTokenDoc ?? null,\n trx: args.trx,\n })\n } catch (error) {\n if (args.trx) throw error\n }\n }\n return { doc, existed, wasDeleted, created, revived }\n}\n\n/**\n * Rebuilds (or clears, when `doc` is null) the search-token rows for a single record.\n * This is the asynchronous-friendly tail of `upsertIndexRow`: it does not touch the\n * `entity_indexes` projection that list endpoints read, so it can run out-of-band\n * without making query-index reads inconsistent.\n */\nexport async function reindexSearchTokensForRecord(\n em: EntityManager,\n args: {\n entityType: string\n recordId: string\n organizationId?: string | null\n tenantId?: string | null\n doc: Record<string, any> | null\n searchTokenDoc?: Record<string, unknown> | null\n trx?: QueryIndexExecutor\n },\n): Promise<void> {\n const db = (em as any).getKysely()\n if (!args.doc) {\n await deleteSearchTokensForRecord(db, {\n entityType: args.entityType,\n recordId: args.recordId,\n organizationId: args.organizationId ?? null,\n tenantId: args.tenantId ?? null,\n }, { trx: args.trx })\n return\n }\n const tokenDoc = args.searchTokenDoc ?? (() => {\n const encryption = resolveTenantEncryptionService(em as any)\n const dekKeyCache = new Map<string | null, string | null>()\n return decryptIndexDocForSearch(\n args.entityType,\n args.doc,\n { tenantId: args.tenantId ?? null, organizationId: args.organizationId ?? null },\n encryption,\n dekKeyCache,\n )\n })()\n // Rebuilt on the decrypted document: the aggregate stored in `entity_indexes` was\n // composed from the row as it sits at rest, so for every encryption-mapped field it\n // holds ciphertext no user query can ever match (#5625). `resolveSearchConfig` re-parses\n // the environment on every call, so it is resolved once and threaded into both the\n // rebuild and the token writer \u2014 that also makes the aggregate's blocklist filter and\n // the per-field one provably read the same snapshot, as the batch path already does.\n const searchConfig = resolveSearchConfig()\n const searchDoc = rebuildAggregateSearchField(await tokenDoc, {\n entityType: args.entityType,\n config: searchConfig,\n })\n await replaceSearchTokensForRecord(db, {\n entityType: args.entityType,\n recordId: args.recordId,\n organizationId: args.organizationId ?? null,\n tenantId: args.tenantId ?? null,\n doc: searchDoc,\n config: searchConfig,\n }, { trx: args.trx })\n}\n\nexport async function markDeleted(\n em: EntityManager,\n args: { entityType: string; recordId: string; organizationId?: string | null; tenantId?: string | null; trx?: QueryIndexExecutor }\n): Promise<{ wasActive: boolean }> {\n const db = (em as any).getKysely()\n const executor = args.trx ?? db\n const existing = await scopeEntityIndexes(\n executor.selectFrom('entity_indexes' as any).select(['deleted_at' as any]),\n args,\n ).executeTakeFirst() as { deleted_at: Date | null } | undefined\n\n const wasActive = !!existing && existing.deleted_at == null\n\n if (existing) {\n try {\n await deleteSearchTokensForRecord(db, {\n entityType: args.entityType,\n recordId: args.recordId,\n organizationId: args.organizationId ?? null,\n tenantId: args.tenantId ?? null,\n }, { trx: args.trx })\n } catch (error) {\n if (args.trx) throw error\n }\n await scopeEntityIndexes(\n executor.deleteFrom('entity_indexes' as any) as any,\n args,\n ).execute()\n }\n\n return { wasActive }\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,4CAA4C;AAErD,SAAS,0BAA0B,sCAAsC;AACzE,SAAS,sCAAsC;AAC/C,SAAS,0BAA0B,iCAAiC;AACpE;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP,SAAwC,WAAW;AACnD,SAAS,oBAAoB;AAC7B,SAAS,2BAA2B;AACpC,SAAS,8BAA8B,mCAAmC;AAC1E,SAAS,4BAA4B,mCAAmC;AACxE,SAAS,8BAA8B;AAEvC,SAAS,uBAAuB,IAAmB,QAAwB;AACzE,SAAO,qCAAqC,IAAI,MAAM,GAAG,SAAS,+BAA+B,IAAI,MAAM;AAC7G;AAEA,MAAM,SAAS,aAAa,aAAa,EAAE,MAAM,EAAE,WAAW,UAAU,CAAC;
|
|
4
|
+
"sourcesContent": ["import { randomUUID } from 'node:crypto'\nimport { resolveQueryIndexSourceTableMetadata } from './subscriber-scope'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { resolveEntityTableName as resolveFallbackEntityTableName } from '@open-mercato/shared/lib/query/engine'\nimport { resolveTenantEncryptionService } from '@open-mercato/shared/lib/encryption/customFieldValues'\nimport { decryptIndexDocForSearch, encryptIndexDocForStorage } from '@open-mercato/shared/lib/encryption/indexDoc'\nimport {\n buildCustomFieldDefinitionIndexFromRows,\n normalizeDefinitionKey,\n selectDefinitionForRecord,\n type CustomFieldDefinitionRow,\n} from '@open-mercato/shared/lib/crud/custom-field-definition-index'\nimport { type Kysely, type Transaction, sql } from 'kysely'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { recordIndexerError } from '@open-mercato/shared/lib/indexers/error-log'\nimport { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'\nimport { resolveSearchConfig } from '@open-mercato/shared/lib/search/config'\nimport { replaceSearchTokensForRecord, deleteSearchTokensForRecord } from './search-tokens'\nimport { attachAggregateSearchField, rebuildAggregateSearchField } from './document'\nimport { applyIndexDocEnrichers } from './doc-enrichers'\n\nfunction resolveEntityTableName(em: EntityManager, entity: string): string {\n return resolveQueryIndexSourceTableMetadata(em, entity)?.table ?? resolveFallbackEntityTableName(em, entity)\n}\n\nconst logger = createLogger('query_index').child({ component: 'indexer' })\n\nfunction reportTranslationsFailure(\n error: unknown,\n code: 'query_index.translations_read_failed' | 'query_index.translations_log_failed',\n): void {\n try {\n getTelemetryRuntime()?.reportError(error, { module: 'query_index', code })\n } catch (telemetryError) {\n logger.warn('Failed to report index translations error', { err: telemetryError })\n }\n}\n\ntype QueryIndexExecutor = Kysely<any> | Transaction<any>\n\ntype BuildDocParams = {\n entityType: string // '<module>:<entity>'\n recordId: string\n organizationId?: string | null\n tenantId?: string | null\n}\n\nexport async function buildIndexDoc(em: EntityManager, params: BuildDocParams): Promise<Record<string, any> | null> {\n const db = (em as any).getKysely()\n const baseTable = resolveEntityTableName(em, params.entityType)\n\n // Fetch base row\n const baseRow = await db\n .selectFrom(baseTable as any)\n .selectAll()\n .where('id' as any, '=', params.recordId)\n .executeTakeFirst() as Record<string, any> | undefined\n if (!baseRow) return null\n const docSources: Array<Record<string, any>> = []\n\n // Attach the core customer entity when indexing customer profiles so search tokens see the combined row\n let parentEntityRow: Record<string, any> | null = null\n if (params.entityType === 'customers:customer_person_profile' || params.entityType === 'customers:customer_company_profile') {\n const entityId = (baseRow as any).entity_id ?? (baseRow as any).entityId\n if (entityId) {\n const entityRow = await db\n .selectFrom('customer_entities' as any)\n .selectAll()\n .where('id' as any, '=', entityId)\n .executeTakeFirst() as Record<string, any> | undefined\n if (entityRow) {\n docSources.push(entityRow)\n parentEntityRow = entityRow\n }\n }\n }\n void parentEntityRow\n\n // Build base document (snake_case keys as in DB)\n let doc: Record<string, any> = {}\n docSources.push(baseRow)\n for (const source of docSources) {\n for (const [k, v] of Object.entries(source)) doc[k] = v\n }\n\n // Attach custom fields under flat keys 'cf:<key>'\n let cfQuery = db\n .selectFrom('custom_field_values' as any)\n .select([\n 'field_key' as any,\n 'value_text' as any,\n 'value_multiline' as any,\n 'value_int' as any,\n 'value_float' as any,\n 'value_bool' as any,\n ])\n .where('entity_id' as any, '=', params.entityType)\n .where('record_id' as any, '=', String(params.recordId))\n\n if (params.organizationId != null) {\n cfQuery = cfQuery.where((eb: any) => eb.or([\n eb('organization_id' as any, '=', params.organizationId),\n eb('organization_id' as any, 'is', null),\n ]))\n } else {\n cfQuery = cfQuery.where('organization_id' as any, 'is', null)\n }\n\n if (params.tenantId != null) {\n cfQuery = cfQuery.where((eb: any) => eb.or([\n eb('tenant_id' as any, '=', params.tenantId),\n eb('tenant_id' as any, 'is', null),\n ]))\n } else {\n cfQuery = cfQuery.where('tenant_id' as any, 'is', null)\n }\n\n const cfRows = await cfQuery.execute() as Array<Record<string, any>>\n\n const cfMap: Record<string, any[]> = {}\n for (const r of cfRows) {\n const key = String(r.field_key)\n const cfKey = `cf:${key}`\n const val = r.value_bool ?? r.value_int ?? r.value_float ?? r.value_text ?? r.value_multiline ?? null\n if (!cfMap[cfKey]) cfMap[cfKey] = []\n cfMap[cfKey].push(val)\n }\n\n const multiKeys = new Set<string>()\n const fieldKeys = Object.keys(cfMap).map((key) => key.slice(3))\n if (fieldKeys.length > 0) {\n let defQuery = db\n .selectFrom('custom_field_defs' as any)\n .select([\n 'key' as any,\n 'kind' as any,\n 'config_json' as any,\n 'organization_id' as any,\n 'tenant_id' as any,\n 'deleted_at' as any,\n 'updated_at' as any,\n ])\n .where('entity_id' as any, '=', params.entityType)\n .where('key' as any, 'in', fieldKeys)\n .where('is_active' as any, '=', true)\n .where('deleted_at' as any, 'is', null)\n\n if (params.organizationId != null) {\n defQuery = defQuery.where((eb: any) => eb.or([\n eb('organization_id' as any, '=', params.organizationId),\n eb('organization_id' as any, 'is', null),\n ]))\n } else {\n defQuery = defQuery.where('organization_id' as any, 'is', null)\n }\n\n if (params.tenantId != null) {\n defQuery = defQuery.where((eb: any) => eb.or([\n eb('tenant_id' as any, '=', params.tenantId),\n eb('tenant_id' as any, 'is', null),\n ]))\n } else {\n defQuery = defQuery.where('tenant_id' as any, 'is', null)\n }\n\n const defRows = await defQuery.execute() as Array<Record<string, any>>\n const definitionIndex = buildCustomFieldDefinitionIndexFromRows(\n defRows.map((row): CustomFieldDefinitionRow => ({\n key: String(row.key ?? ''),\n entityId: params.entityType,\n kind: typeof row.kind === 'string' ? row.kind : null,\n configJson: row.config_json,\n organizationId: row.organization_id ?? null,\n tenantId: row.tenant_id ?? null,\n deletedAt: row.deleted_at ?? null,\n updatedAt: row.updated_at ?? null,\n })),\n { organizationIds: params.organizationId ? [params.organizationId] : [] },\n )\n for (const key of fieldKeys) {\n const definitions = definitionIndex.get(normalizeDefinitionKey(key)) ?? []\n const definition = selectDefinitionForRecord(\n definitions,\n params.organizationId ?? null,\n params.tenantId ?? null,\n )\n if (definition?.multi) multiKeys.add(key)\n }\n }\n\n for (const [key, arr] of Object.entries(cfMap)) {\n // Cardinality belongs to the field definition, not the current row count:\n // a multi field containing one value must still hydrate as an array.\n const fieldKey = key.slice(3)\n doc[key] = multiKeys.has(fieldKey) || arr.length > 1 ? arr : arr[0]\n }\n\n // Attach translations under flat keys 'l10n:{locale}:{field}'\n const translationsSavepoint = db.isTransaction ? `om_translations_${randomUUID()}` : null\n if (translationsSavepoint) await sql`SAVEPOINT ${sql.id(translationsSavepoint)}`.execute(db)\n try {\n const translationRow = await db\n .selectFrom('entity_translations' as any)\n .select(['translations' as any])\n .where('entity_type' as any, '=', params.entityType)\n .where('entity_id' as any, '=', String(params.recordId))\n .where(sql`tenant_id is not distinct from ${params.tenantId ?? null}`)\n .where(sql`organization_id is not distinct from ${params.organizationId ?? null}`)\n .executeTakeFirst() as { translations: Record<string, Record<string, unknown>> | null } | undefined\n\n if (translationRow?.translations && typeof translationRow.translations === 'object') {\n for (const [locale, fields] of Object.entries(translationRow.translations)) {\n if (!fields || typeof fields !== 'object') continue\n for (const [field, value] of Object.entries(fields as Record<string, unknown>)) {\n if (typeof value === 'string' && value.length > 0) {\n doc[`l10n:${locale}:${field}`] = value\n }\n }\n }\n }\n } catch (translationsError) {\n if (translationsSavepoint) {\n await sql`ROLLBACK TO SAVEPOINT ${sql.id(translationsSavepoint)}`.execute(db)\n }\n reportTranslationsFailure(translationsError, 'query_index.translations_read_failed')\n await recordIndexerError(\n { db },\n {\n source: 'query_index',\n handler: 'query_index:build-index-doc:translations',\n error: translationsError,\n entityType: params.entityType,\n recordId: params.recordId,\n tenantId: params.tenantId ?? null,\n organizationId: params.organizationId ?? null,\n },\n ).catch((loggingError: unknown) => reportTranslationsFailure(loggingError, 'query_index.translations_log_failed'))\n } finally {\n if (translationsSavepoint) {\n try {\n await sql`RELEASE SAVEPOINT ${sql.id(translationsSavepoint)}`.execute(db)\n } catch (loggingError) {\n await sql`ROLLBACK TO SAVEPOINT ${sql.id(translationsSavepoint)}`.execute(db)\n await sql`RELEASE SAVEPOINT ${sql.id(translationsSavepoint)}`.execute(db)\n reportTranslationsFailure(loggingError, 'query_index.translations_log_failed')\n }\n }\n }\n\n await applyIndexDocEnrichers(db, params.entityType, [{\n recordId: String(params.recordId),\n doc,\n tenantId: params.tenantId ?? null,\n organizationId: params.organizationId ?? null,\n }])\n\n // Kept outside the guard below: a failure while building the aggregate search field is a\n // bug in the aggregation or its configuration, and must surface instead of being mistaken\n // for an encryption failure and silently skipping encryption.\n doc = attachAggregateSearchField(doc, { entityType: params.entityType })\n\n try {\n const encryption = resolveTenantEncryptionService(em as any)\n doc = await encryptIndexDocForStorage(\n params.entityType,\n doc,\n { tenantId: params.tenantId ?? null, organizationId: params.organizationId ?? null },\n encryption,\n )\n } catch (error) {\n // `encryptIndexDocForStorage` already returns the document untouched when encryption is\n // absent or disabled, so a throw here is always a genuine encryption failure. Returning\n // `doc` would hand the caller the plaintext document to write into `entity_indexes`,\n // which must stay encrypted at rest; returning `null` would mean \"record gone\" and make\n // `upsertIndexRow` delete a healthy index row. Fail loudly instead: the projection keeps\n // its previous, correctly encrypted contents and the `query_index.upsert_one` subscriber\n // persists the error via `recordIndexerError`.\n logger.error('Index document encryption failed; refusing to return an unencrypted document', {\n entityType: params.entityType,\n recordId: params.recordId,\n tenantId: params.tenantId ?? null,\n organizationId: params.organizationId ?? null,\n err: error,\n })\n throw error\n }\n\n return doc\n}\n\nexport type UpsertIndexResult = {\n doc: Record<string, any> | null\n existed: boolean\n wasDeleted: boolean\n created: boolean\n revived: boolean\n}\n\nfunction scopeEntityIndexes<QB extends { where: (...args: any[]) => QB }>(\n q: QB,\n args: { entityType: string; recordId: string; organizationId?: string | null; tenantId?: string | null },\n): QB {\n let chain = q.where('entity_type' as any, '=', args.entityType)\n chain = chain.where('entity_id' as any, '=', String(args.recordId))\n chain = args.organizationId == null\n ? chain.where('organization_id' as any, 'is', null as any)\n : chain.where('organization_id' as any, '=', args.organizationId)\n chain = chain.where(sql`tenant_id is not distinct from ${args.tenantId ?? null}`)\n return chain\n}\n\nexport async function upsertIndexRow(\n em: EntityManager,\n args: { entityType: string; recordId: string; organizationId?: string | null; tenantId?: string | null; searchTokenDoc?: Record<string, unknown> | null; deferSearchTokens?: boolean; trx?: QueryIndexExecutor }\n): Promise<UpsertIndexResult> {\n const db = (em as any).getKysely()\n const executor = args.trx ?? db\n\n const existing = await scopeEntityIndexes(\n executor.selectFrom('entity_indexes' as any).select(['id' as any, 'deleted_at' as any]),\n args,\n ).executeTakeFirst() as { id: string; deleted_at: Date | null } | undefined\n\n const existed = !!existing\n const wasDeleted = !!existing && existing.deleted_at != null\n\n const doc = await buildIndexDoc(em, args)\n if (!doc) {\n // When the caller defers token work it owns the matching token cleanup; the\n // projection-row removal below stays synchronous so list reads converge immediately.\n if (!args.deferSearchTokens) {\n try {\n await reindexSearchTokensForRecord(em, {\n entityType: args.entityType,\n recordId: args.recordId,\n organizationId: args.organizationId ?? null,\n tenantId: args.tenantId ?? null,\n doc: null,\n trx: args.trx,\n })\n } catch (error) {\n if (args.trx) throw error\n }\n }\n if (existed) {\n await scopeEntityIndexes(\n executor.deleteFrom('entity_indexes' as any) as any,\n args,\n ).execute()\n }\n return { doc: null, existed, wasDeleted, created: false, revived: false }\n }\n\n const payload = {\n entity_type: args.entityType,\n entity_id: String(args.recordId),\n organization_id: args.organizationId ?? null,\n tenant_id: args.tenantId ?? null,\n doc: sql`${JSON.stringify(doc)}::jsonb`,\n index_version: 1,\n updated_at: sql`now()`,\n deleted_at: null,\n }\n\n // Prefer modern upsert keyed by coalesced org id when available; fallback to update-then-insert\n try {\n await executor\n .insertInto('entity_indexes' as any)\n .values({ ...payload, created_at: sql`now()` } as any)\n .onConflict((oc: any) => oc\n .columns(['entity_type', 'entity_id', 'organization_id_coalesced'])\n .doUpdateSet({\n tenant_id: args.tenantId ?? null,\n doc: sql`${JSON.stringify(doc)}::jsonb`,\n index_version: 1,\n updated_at: sql`now()`,\n deleted_at: null,\n } as any))\n .execute()\n } catch {\n // Fallback for schemas without organization_id_coalesced column/index\n const updated = await scopeEntityIndexes(\n executor.updateTable('entity_indexes' as any).set(payload as any) as any,\n args,\n ).executeTakeFirst() as { numUpdatedRows?: bigint | number } | undefined\n if (!updated || Number(updated.numUpdatedRows ?? 0) === 0) {\n try {\n await executor\n .insertInto('entity_indexes' as any)\n .values({ ...payload, created_at: sql`now()` } as any)\n .execute()\n } catch (error) {\n if (args.trx) throw error\n }\n }\n }\n\n const created = !existed\n const revived = existed && wasDeleted\n // The search-token rebuild (DELETE + chunked INSERT) is the heavy tail of indexing.\n // Callers that defer it (the upsert subscriber) run `reindexSearchTokensForRecord`\n // asynchronously after this projection update so write latency stays bounded.\n if (!args.deferSearchTokens) {\n try {\n await reindexSearchTokensForRecord(em, {\n entityType: args.entityType,\n recordId: args.recordId,\n organizationId: args.organizationId ?? null,\n tenantId: args.tenantId ?? null,\n doc,\n searchTokenDoc: args.searchTokenDoc ?? null,\n trx: args.trx,\n })\n } catch (error) {\n if (args.trx) throw error\n }\n }\n return { doc, existed, wasDeleted, created, revived }\n}\n\n/**\n * Rebuilds (or clears, when `doc` is null) the search-token rows for a single record.\n * This is the asynchronous-friendly tail of `upsertIndexRow`: it does not touch the\n * `entity_indexes` projection that list endpoints read, so it can run out-of-band\n * without making query-index reads inconsistent.\n */\nexport async function reindexSearchTokensForRecord(\n em: EntityManager,\n args: {\n entityType: string\n recordId: string\n organizationId?: string | null\n tenantId?: string | null\n doc: Record<string, any> | null\n searchTokenDoc?: Record<string, unknown> | null\n trx?: QueryIndexExecutor\n },\n): Promise<void> {\n const db = (em as any).getKysely()\n if (!args.doc) {\n await deleteSearchTokensForRecord(db, {\n entityType: args.entityType,\n recordId: args.recordId,\n organizationId: args.organizationId ?? null,\n tenantId: args.tenantId ?? null,\n }, { trx: args.trx })\n return\n }\n const tokenDoc = args.searchTokenDoc ?? (() => {\n const encryption = resolveTenantEncryptionService(em as any)\n const dekKeyCache = new Map<string | null, string | null>()\n return decryptIndexDocForSearch(\n args.entityType,\n args.doc,\n { tenantId: args.tenantId ?? null, organizationId: args.organizationId ?? null },\n encryption,\n dekKeyCache,\n )\n })()\n // Rebuilt on the decrypted document: the aggregate stored in `entity_indexes` was\n // composed from the row as it sits at rest, so for every encryption-mapped field it\n // holds ciphertext no user query can ever match (#5625). `resolveSearchConfig` re-parses\n // the environment on every call, so it is resolved once and threaded into both the\n // rebuild and the token writer \u2014 that also makes the aggregate's blocklist filter and\n // the per-field one provably read the same snapshot, as the batch path already does.\n const searchConfig = resolveSearchConfig()\n const searchDoc = rebuildAggregateSearchField(await tokenDoc, {\n entityType: args.entityType,\n config: searchConfig,\n })\n await replaceSearchTokensForRecord(db, {\n entityType: args.entityType,\n recordId: args.recordId,\n organizationId: args.organizationId ?? null,\n tenantId: args.tenantId ?? null,\n doc: searchDoc,\n config: searchConfig,\n }, { trx: args.trx })\n}\n\nexport async function markDeleted(\n em: EntityManager,\n args: { entityType: string; recordId: string; organizationId?: string | null; tenantId?: string | null; trx?: QueryIndexExecutor }\n): Promise<{ wasActive: boolean }> {\n const db = (em as any).getKysely()\n const executor = args.trx ?? db\n const existing = await scopeEntityIndexes(\n executor.selectFrom('entity_indexes' as any).select(['deleted_at' as any]),\n args,\n ).executeTakeFirst() as { deleted_at: Date | null } | undefined\n\n const wasActive = !!existing && existing.deleted_at == null\n\n if (existing) {\n try {\n await deleteSearchTokensForRecord(db, {\n entityType: args.entityType,\n recordId: args.recordId,\n organizationId: args.organizationId ?? null,\n tenantId: args.tenantId ?? null,\n }, { trx: args.trx })\n } catch (error) {\n if (args.trx) throw error\n }\n await scopeEntityIndexes(\n executor.deleteFrom('entity_indexes' as any) as any,\n args,\n ).execute()\n }\n\n return { wasActive }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -35,7 +35,7 @@ function WarrantyWorkspace({
|
|
|
35
35
|
)) }) }) : null,
|
|
36
36
|
/* @__PURE__ */ jsx("div", { className: cn(
|
|
37
37
|
"min-w-0 [&>[data-component-handle]>div:first-child_[data-slot=button]]:h-8 [&>[data-component-handle]>div:first-child_[data-slot=search-input-wrapper]]:h-8 [&_[data-slot=table-header]_[data-slot=table-row]]:h-9 [&_[data-slot=table-body]_[data-slot=table-row]]:h-16",
|
|
38
|
-
"md:[&_[data-slot=table-head][class*=sticky]]:bg-card md:[&_[data-slot=table-cell][class*=sticky]]:bg-card",
|
|
38
|
+
"[&_[data-slot=table-header]]:bg-card md:[&_[data-slot=table-head][class*=sticky]]:bg-card md:[&_[data-slot=table-cell][class*=sticky]]:bg-card",
|
|
39
39
|
"md:[&_[data-slot=table-head][class*=sticky]]:bg-linear-to-r md:[&_[data-slot=table-head][class*=sticky]]:from-muted/40 md:[&_[data-slot=table-head][class*=sticky]]:to-muted/40",
|
|
40
40
|
"md:[&_[data-slot=table-row]:hover_[data-slot=table-cell][class*=sticky]]:bg-linear-to-r md:[&_[data-slot=table-row]:hover_[data-slot=table-cell][class*=sticky]]:from-muted/30 md:[&_[data-slot=table-row]:hover_[data-slot=table-cell][class*=sticky]]:to-muted/30",
|
|
41
41
|
contentClassName
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/warranty_claims/backend/components/WarrantyWorkspace.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Tabs, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'\n\nexport type WarrantyWorkspaceTab = {\n id: string\n label: string\n count?: number\n disabled?: boolean\n}\n\ntype WarrantyWorkspaceProps = {\n title: string\n description?: string\n action?: React.ReactNode\n tabs?: WarrantyWorkspaceTab[]\n activeTab?: string\n onTabChange?: (value: string) => void\n summary?: React.ReactNode\n contentClassName?: string\n children: React.ReactNode\n}\n\nexport function WarrantyWorkspace({\n title,\n description,\n action,\n tabs,\n activeTab,\n onTabChange,\n summary,\n contentClassName,\n children,\n}: WarrantyWorkspaceProps) {\n return (\n <section className=\"overflow-hidden rounded-xl border border-border bg-card\">\n <div className=\"flex flex-col gap-4 px-8 pb-6 pt-7 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"min-w-0\">\n <h1 className=\"text-2xl font-bold tracking-tight text-foreground\">{title}</h1>\n {description ? <p className=\"mt-1 text-sm text-muted-foreground\">{description}</p> : null}\n </div>\n {action ? <div className=\"shrink-0\">{action}</div> : null}\n </div>\n {summary ?? null}\n {tabs?.length && activeTab && onTabChange ? (\n <Tabs value={activeTab} onValueChange={onTabChange} variant=\"underline\">\n <TabsList className=\"flex h-auto w-full gap-1 overflow-x-auto px-7 pb-2\" aria-label={title}>\n {tabs.map((tab) => (\n <TabsTrigger\n key={tab.id}\n value={tab.id}\n count={tab.count}\n disabled={tab.disabled}\n className=\"shrink-0 gap-2 px-4 pb-2 pt-3 [&_[data-slot=tabs-trigger-count]]:h-auto [&_[data-slot=tabs-trigger-count]]:min-w-0 [&_[data-slot=tabs-trigger-count]]:rounded-md [&_[data-slot=tabs-trigger-count]]:bg-muted [&_[data-slot=tabs-trigger-count]]:px-1.5 [&_[data-slot=tabs-trigger-count]]:py-0.5 [&_[data-slot=tabs-trigger-count]]:text-overline [&_[data-slot=tabs-trigger-count]]:text-muted-foreground\"\n >\n {tab.label}\n </TabsTrigger>\n ))}\n </TabsList>\n </Tabs>\n ) : null}\n <div className={cn(\n 'min-w-0 [&>[data-component-handle]>div:first-child_[data-slot=button]]:h-8 [&>[data-component-handle]>div:first-child_[data-slot=search-input-wrapper]]:h-8 [&_[data-slot=table-header]_[data-slot=table-row]]:h-9 [&_[data-slot=table-body]_[data-slot=table-row]]:h-16',\n 'md:[&_[data-slot=table-head][class*=sticky]]:bg-card md:[&_[data-slot=table-cell][class*=sticky]]:bg-card',\n 'md:[&_[data-slot=table-head][class*=sticky]]:bg-linear-to-r md:[&_[data-slot=table-head][class*=sticky]]:from-muted/40 md:[&_[data-slot=table-head][class*=sticky]]:to-muted/40',\n 'md:[&_[data-slot=table-row]:hover_[data-slot=table-cell][class*=sticky]]:bg-linear-to-r md:[&_[data-slot=table-row]:hover_[data-slot=table-cell][class*=sticky]]:from-muted/30 md:[&_[data-slot=table-row]:hover_[data-slot=table-cell][class*=sticky]]:to-muted/30',\n contentClassName,\n )}>{children}</div>\n </section>\n )\n}\n"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Tabs, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'\n\nexport type WarrantyWorkspaceTab = {\n id: string\n label: string\n count?: number\n disabled?: boolean\n}\n\ntype WarrantyWorkspaceProps = {\n title: string\n description?: string\n action?: React.ReactNode\n tabs?: WarrantyWorkspaceTab[]\n activeTab?: string\n onTabChange?: (value: string) => void\n summary?: React.ReactNode\n contentClassName?: string\n children: React.ReactNode\n}\n\nexport function WarrantyWorkspace({\n title,\n description,\n action,\n tabs,\n activeTab,\n onTabChange,\n summary,\n contentClassName,\n children,\n}: WarrantyWorkspaceProps) {\n return (\n <section className=\"overflow-hidden rounded-xl border border-border bg-card\">\n <div className=\"flex flex-col gap-4 px-8 pb-6 pt-7 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"min-w-0\">\n <h1 className=\"text-2xl font-bold tracking-tight text-foreground\">{title}</h1>\n {description ? <p className=\"mt-1 text-sm text-muted-foreground\">{description}</p> : null}\n </div>\n {action ? <div className=\"shrink-0\">{action}</div> : null}\n </div>\n {summary ?? null}\n {tabs?.length && activeTab && onTabChange ? (\n <Tabs value={activeTab} onValueChange={onTabChange} variant=\"underline\">\n <TabsList className=\"flex h-auto w-full gap-1 overflow-x-auto px-7 pb-2\" aria-label={title}>\n {tabs.map((tab) => (\n <TabsTrigger\n key={tab.id}\n value={tab.id}\n count={tab.count}\n disabled={tab.disabled}\n className=\"shrink-0 gap-2 px-4 pb-2 pt-3 [&_[data-slot=tabs-trigger-count]]:h-auto [&_[data-slot=tabs-trigger-count]]:min-w-0 [&_[data-slot=tabs-trigger-count]]:rounded-md [&_[data-slot=tabs-trigger-count]]:bg-muted [&_[data-slot=tabs-trigger-count]]:px-1.5 [&_[data-slot=tabs-trigger-count]]:py-0.5 [&_[data-slot=tabs-trigger-count]]:text-overline [&_[data-slot=tabs-trigger-count]]:text-muted-foreground\"\n >\n {tab.label}\n </TabsTrigger>\n ))}\n </TabsList>\n </Tabs>\n ) : null}\n <div className={cn(\n 'min-w-0 [&>[data-component-handle]>div:first-child_[data-slot=button]]:h-8 [&>[data-component-handle]>div:first-child_[data-slot=search-input-wrapper]]:h-8 [&_[data-slot=table-header]_[data-slot=table-row]]:h-9 [&_[data-slot=table-body]_[data-slot=table-row]]:h-16',\n '[&_[data-slot=table-header]]:bg-card md:[&_[data-slot=table-head][class*=sticky]]:bg-card md:[&_[data-slot=table-cell][class*=sticky]]:bg-card',\n 'md:[&_[data-slot=table-head][class*=sticky]]:bg-linear-to-r md:[&_[data-slot=table-head][class*=sticky]]:from-muted/40 md:[&_[data-slot=table-head][class*=sticky]]:to-muted/40',\n 'md:[&_[data-slot=table-row]:hover_[data-slot=table-cell][class*=sticky]]:bg-linear-to-r md:[&_[data-slot=table-row]:hover_[data-slot=table-cell][class*=sticky]]:from-muted/30 md:[&_[data-slot=table-row]:hover_[data-slot=table-cell][class*=sticky]]:to-muted/30',\n contentClassName,\n )}>{children}</div>\n </section>\n )\n}\n"],
|
|
5
5
|
"mappings": ";AAuCQ,SACE,KADF;AApCR,SAAS,UAAU;AACnB,SAAS,MAAM,UAAU,mBAAmB;AAqBrC,SAAS,kBAAkB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA2B;AACzB,SACE,qBAAC,aAAQ,WAAU,2DACjB;AAAA,yBAAC,SAAI,WAAU,qFACb;AAAA,2BAAC,SAAI,WAAU,WACb;AAAA,4BAAC,QAAG,WAAU,qDAAqD,iBAAM;AAAA,QACxE,cAAc,oBAAC,OAAE,WAAU,sCAAsC,uBAAY,IAAO;AAAA,SACvF;AAAA,MACC,SAAS,oBAAC,SAAI,WAAU,YAAY,kBAAO,IAAS;AAAA,OACvD;AAAA,IACC,WAAW;AAAA,IACX,MAAM,UAAU,aAAa,cAC5B,oBAAC,QAAK,OAAO,WAAW,eAAe,aAAa,SAAQ,aAC1D,8BAAC,YAAS,WAAU,sDAAqD,cAAY,OAClF,eAAK,IAAI,CAAC,QACT;AAAA,MAAC;AAAA;AAAA,QAEC,OAAO,IAAI;AAAA,QACX,OAAO,IAAI;AAAA,QACX,UAAU,IAAI;AAAA,QACd,WAAU;AAAA,QAET,cAAI;AAAA;AAAA,MANA,IAAI;AAAA,IAOX,CACD,GACH,GACF,IACE;AAAA,IACJ,oBAAC,SAAI,WAAW;AAAA,MACd;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,GAAI,UAAS;AAAA,KACf;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.10.1-develop.
|
|
3
|
+
"version": "0.10.1-develop.7932.1.ead5057eae",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -266,16 +266,16 @@
|
|
|
266
266
|
"zod": "^4.6.6"
|
|
267
267
|
},
|
|
268
268
|
"peerDependencies": {
|
|
269
|
-
"@open-mercato/ai-assistant": "0.10.1-develop.
|
|
270
|
-
"@open-mercato/shared": "0.10.1-develop.
|
|
271
|
-
"@open-mercato/ui": "0.10.1-develop.
|
|
269
|
+
"@open-mercato/ai-assistant": "0.10.1-develop.7932.1.ead5057eae",
|
|
270
|
+
"@open-mercato/shared": "0.10.1-develop.7932.1.ead5057eae",
|
|
271
|
+
"@open-mercato/ui": "0.10.1-develop.7932.1.ead5057eae",
|
|
272
272
|
"react": "^19.0.0",
|
|
273
273
|
"react-dom": "^19.0.0"
|
|
274
274
|
},
|
|
275
275
|
"devDependencies": {
|
|
276
|
-
"@open-mercato/ai-assistant": "0.10.1-develop.
|
|
277
|
-
"@open-mercato/shared": "0.10.1-develop.
|
|
278
|
-
"@open-mercato/ui": "0.10.1-develop.
|
|
276
|
+
"@open-mercato/ai-assistant": "0.10.1-develop.7932.1.ead5057eae",
|
|
277
|
+
"@open-mercato/shared": "0.10.1-develop.7932.1.ead5057eae",
|
|
278
|
+
"@open-mercato/ui": "0.10.1-develop.7932.1.ead5057eae",
|
|
279
279
|
"@testing-library/dom": "^10.4.1",
|
|
280
280
|
"@testing-library/jest-dom": "^7.0.1",
|
|
281
281
|
"@testing-library/react": "^16.3.3",
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
requireId,
|
|
10
10
|
} from '@open-mercato/shared/lib/commands/helpers'
|
|
11
11
|
import type { CrudEventsConfig, CrudIndexerConfig } from '@open-mercato/shared/lib/crud/types'
|
|
12
|
-
import { CrudHttpError, forbidden } from '@open-mercato/shared/lib/crud/errors'
|
|
12
|
+
import { CrudHttpError, forbidden, isUniqueViolation } from '@open-mercato/shared/lib/crud/errors'
|
|
13
13
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
14
14
|
import type { DataEngine } from '@open-mercato/shared/lib/data/engine'
|
|
15
15
|
import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
@@ -192,13 +192,25 @@ const createRoleCommand: CommandHandler<Record<string, unknown>, Role> = {
|
|
|
192
192
|
throw new CrudHttpError(400, { error: 'tenantId is required — global roles are not supported' })
|
|
193
193
|
}
|
|
194
194
|
const de = (ctx.container.resolve('dataEngine') as DataEngine)
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
195
|
+
let role: Role
|
|
196
|
+
try {
|
|
197
|
+
role = await de.createOrmEntity({
|
|
198
|
+
entity: Role,
|
|
199
|
+
data: {
|
|
200
|
+
name: parsed.name,
|
|
201
|
+
tenantId: resolvedTenantId,
|
|
202
|
+
},
|
|
203
|
+
})
|
|
204
|
+
} catch (error) {
|
|
205
|
+
if (!isUniqueViolation(error, 'roles_tenant_id_name_unique')) throw error
|
|
206
|
+
const { translate } = await resolveTranslations()
|
|
207
|
+
const message = translate('auth.roles.errors.nameExists', 'A role with this name already exists')
|
|
208
|
+
throw new CrudHttpError(400, {
|
|
209
|
+
error: message,
|
|
210
|
+
fieldErrors: { name: message },
|
|
211
|
+
details: [{ path: ['name'], message, code: 'duplicate', origin: 'validation' }],
|
|
212
|
+
})
|
|
213
|
+
}
|
|
202
214
|
|
|
203
215
|
await setCustomFieldsIfAny({
|
|
204
216
|
dataEngine: de,
|
|
@@ -614,6 +614,7 @@
|
|
|
614
614
|
"auth.resetPassword": "Passwort zurücksetzen",
|
|
615
615
|
"auth.roles.admin": "Administrator",
|
|
616
616
|
"auth.roles.employee": "Mitarbeitende",
|
|
617
|
+
"auth.roles.errors.nameExists": "Eine Rolle mit diesem Namen existiert bereits",
|
|
617
618
|
"auth.roles.flash.created": "Rolle erstellt",
|
|
618
619
|
"auth.roles.flash.deleted": "Rolle gelöscht",
|
|
619
620
|
"auth.roles.flash.updated": "Rolle gespeichert",
|
|
@@ -614,6 +614,7 @@
|
|
|
614
614
|
"auth.resetPassword": "Reset password",
|
|
615
615
|
"auth.roles.admin": "Admin",
|
|
616
616
|
"auth.roles.employee": "Employee",
|
|
617
|
+
"auth.roles.errors.nameExists": "A role with this name already exists",
|
|
617
618
|
"auth.roles.flash.created": "Role created",
|
|
618
619
|
"auth.roles.flash.deleted": "Role deleted",
|
|
619
620
|
"auth.roles.flash.updated": "Role saved",
|
|
@@ -614,6 +614,7 @@
|
|
|
614
614
|
"auth.resetPassword": "Restablecer contraseña",
|
|
615
615
|
"auth.roles.admin": "Administrador",
|
|
616
616
|
"auth.roles.employee": "Empleado",
|
|
617
|
+
"auth.roles.errors.nameExists": "Ya existe un rol con este nombre",
|
|
617
618
|
"auth.roles.flash.created": "Rol creado",
|
|
618
619
|
"auth.roles.flash.deleted": "Rol eliminado",
|
|
619
620
|
"auth.roles.flash.updated": "Rol guardado",
|
|
@@ -614,6 +614,7 @@
|
|
|
614
614
|
"auth.resetPassword": "비밀번호 재설정",
|
|
615
615
|
"auth.roles.admin": "관리자",
|
|
616
616
|
"auth.roles.employee": "직원",
|
|
617
|
+
"auth.roles.errors.nameExists": "이 이름의 역할이 이미 존재합니다",
|
|
617
618
|
"auth.roles.flash.created": "역할이 생성되었습니다",
|
|
618
619
|
"auth.roles.flash.deleted": "역할이 삭제되었습니다",
|
|
619
620
|
"auth.roles.flash.updated": "역할이 저장되었습니다",
|
|
@@ -614,6 +614,7 @@
|
|
|
614
614
|
"auth.resetPassword": "Resetuj hasło",
|
|
615
615
|
"auth.roles.admin": "Administrator",
|
|
616
616
|
"auth.roles.employee": "Pracownik",
|
|
617
|
+
"auth.roles.errors.nameExists": "Rola o tej nazwie już istnieje",
|
|
617
618
|
"auth.roles.flash.created": "Rola utworzona",
|
|
618
619
|
"auth.roles.flash.deleted": "Rola usunięta",
|
|
619
620
|
"auth.roles.flash.updated": "Rola zapisana",
|
|
@@ -240,7 +240,7 @@
|
|
|
240
240
|
"business_rules.rules.form.placeholders.entityType": "Tippen Sie, um Vorschläge zu sehen",
|
|
241
241
|
"business_rules.rules.form.placeholders.eventType": "Tippen Sie, um Vorschläge zu sehen",
|
|
242
242
|
"business_rules.rules.form.placeholders.ruleCategory": "z.B. Preisgestaltung, Workflow, Datenqualität",
|
|
243
|
-
"business_rules.rules.form.placeholders.ruleId": "
|
|
243
|
+
"business_rules.rules.form.placeholders.ruleId": "ORDER_APPROVAL_CHECK",
|
|
244
244
|
"business_rules.rules.form.placeholders.ruleName": "Geben Sie einen beschreibenden Regelnamen ein",
|
|
245
245
|
"business_rules.rules.form.priority": "Priorität",
|
|
246
246
|
"business_rules.rules.form.ruleCategory": "Kategorie",
|
|
@@ -240,7 +240,7 @@
|
|
|
240
240
|
"business_rules.rules.form.placeholders.entityType": "Start typing to see suggestions",
|
|
241
241
|
"business_rules.rules.form.placeholders.eventType": "Start typing to see suggestions",
|
|
242
242
|
"business_rules.rules.form.placeholders.ruleCategory": "e.g., Pricing, Workflow, Data Quality",
|
|
243
|
-
"business_rules.rules.form.placeholders.ruleId": "
|
|
243
|
+
"business_rules.rules.form.placeholders.ruleId": "ORDER_APPROVAL_CHECK",
|
|
244
244
|
"business_rules.rules.form.placeholders.ruleName": "Enter a descriptive rule name",
|
|
245
245
|
"business_rules.rules.form.priority": "Priority",
|
|
246
246
|
"business_rules.rules.form.ruleCategory": "Category",
|
|
@@ -240,7 +240,7 @@
|
|
|
240
240
|
"business_rules.rules.form.placeholders.entityType": "Empiece a escribir para ver sugerencias",
|
|
241
241
|
"business_rules.rules.form.placeholders.eventType": "Empiece a escribir para ver sugerencias",
|
|
242
242
|
"business_rules.rules.form.placeholders.ruleCategory": "ej. Precios, Flujo de trabajo, Calidad de datos",
|
|
243
|
-
"business_rules.rules.form.placeholders.ruleId": "
|
|
243
|
+
"business_rules.rules.form.placeholders.ruleId": "ORDER_APPROVAL_CHECK",
|
|
244
244
|
"business_rules.rules.form.placeholders.ruleName": "Ingrese un nombre descriptivo para la regla",
|
|
245
245
|
"business_rules.rules.form.priority": "Prioridad",
|
|
246
246
|
"business_rules.rules.form.ruleCategory": "Categoría",
|
|
@@ -240,7 +240,7 @@
|
|
|
240
240
|
"business_rules.rules.form.placeholders.entityType": "입력을 시작하면 제안이 표시됩니다",
|
|
241
241
|
"business_rules.rules.form.placeholders.eventType": "입력을 시작하면 제안이 표시됩니다",
|
|
242
242
|
"business_rules.rules.form.placeholders.ruleCategory": "예: 가격 책정, 워크플로, 데이터 품질",
|
|
243
|
-
"business_rules.rules.form.placeholders.ruleId": "
|
|
243
|
+
"business_rules.rules.form.placeholders.ruleId": "ORDER_APPROVAL_CHECK",
|
|
244
244
|
"business_rules.rules.form.placeholders.ruleName": "설명적인 규칙 이름을 입력하세요",
|
|
245
245
|
"business_rules.rules.form.priority": "우선순위",
|
|
246
246
|
"business_rules.rules.form.ruleCategory": "카테고리",
|
|
@@ -240,7 +240,7 @@
|
|
|
240
240
|
"business_rules.rules.form.placeholders.entityType": "Zacznij pisać, aby zobaczyć podpowiedzi",
|
|
241
241
|
"business_rules.rules.form.placeholders.eventType": "Zacznij pisać, aby zobaczyć podpowiedzi",
|
|
242
242
|
"business_rules.rules.form.placeholders.ruleCategory": "np. Cenowanie, Przepływ pracy, Jakość danych",
|
|
243
|
-
"business_rules.rules.form.placeholders.ruleId": "
|
|
243
|
+
"business_rules.rules.form.placeholders.ruleId": "ORDER_APPROVAL_CHECK",
|
|
244
244
|
"business_rules.rules.form.placeholders.ruleName": "Wprowadź opisową nazwę reguły",
|
|
245
245
|
"business_rules.rules.form.priority": "Priorytet",
|
|
246
246
|
"business_rules.rules.form.ruleCategory": "Kategoria",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto'
|
|
1
2
|
import { resolveQueryIndexSourceTableMetadata } from './subscriber-scope'
|
|
2
3
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
3
4
|
import { resolveEntityTableName as resolveFallbackEntityTableName } from '@open-mercato/shared/lib/query/engine'
|
|
@@ -11,6 +12,8 @@ import {
|
|
|
11
12
|
} from '@open-mercato/shared/lib/crud/custom-field-definition-index'
|
|
12
13
|
import { type Kysely, type Transaction, sql } from 'kysely'
|
|
13
14
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
15
|
+
import { recordIndexerError } from '@open-mercato/shared/lib/indexers/error-log'
|
|
16
|
+
import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
|
|
14
17
|
import { resolveSearchConfig } from '@open-mercato/shared/lib/search/config'
|
|
15
18
|
import { replaceSearchTokensForRecord, deleteSearchTokensForRecord } from './search-tokens'
|
|
16
19
|
import { attachAggregateSearchField, rebuildAggregateSearchField } from './document'
|
|
@@ -22,6 +25,17 @@ function resolveEntityTableName(em: EntityManager, entity: string): string {
|
|
|
22
25
|
|
|
23
26
|
const logger = createLogger('query_index').child({ component: 'indexer' })
|
|
24
27
|
|
|
28
|
+
function reportTranslationsFailure(
|
|
29
|
+
error: unknown,
|
|
30
|
+
code: 'query_index.translations_read_failed' | 'query_index.translations_log_failed',
|
|
31
|
+
): void {
|
|
32
|
+
try {
|
|
33
|
+
getTelemetryRuntime()?.reportError(error, { module: 'query_index', code })
|
|
34
|
+
} catch (telemetryError) {
|
|
35
|
+
logger.warn('Failed to report index translations error', { err: telemetryError })
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
25
39
|
type QueryIndexExecutor = Kysely<any> | Transaction<any>
|
|
26
40
|
|
|
27
41
|
type BuildDocParams = {
|
|
@@ -182,6 +196,8 @@ export async function buildIndexDoc(em: EntityManager, params: BuildDocParams):
|
|
|
182
196
|
}
|
|
183
197
|
|
|
184
198
|
// Attach translations under flat keys 'l10n:{locale}:{field}'
|
|
199
|
+
const translationsSavepoint = db.isTransaction ? `om_translations_${randomUUID()}` : null
|
|
200
|
+
if (translationsSavepoint) await sql`SAVEPOINT ${sql.id(translationsSavepoint)}`.execute(db)
|
|
185
201
|
try {
|
|
186
202
|
const translationRow = await db
|
|
187
203
|
.selectFrom('entity_translations' as any)
|
|
@@ -202,7 +218,34 @@ export async function buildIndexDoc(em: EntityManager, params: BuildDocParams):
|
|
|
202
218
|
}
|
|
203
219
|
}
|
|
204
220
|
}
|
|
205
|
-
} catch {
|
|
221
|
+
} catch (translationsError) {
|
|
222
|
+
if (translationsSavepoint) {
|
|
223
|
+
await sql`ROLLBACK TO SAVEPOINT ${sql.id(translationsSavepoint)}`.execute(db)
|
|
224
|
+
}
|
|
225
|
+
reportTranslationsFailure(translationsError, 'query_index.translations_read_failed')
|
|
226
|
+
await recordIndexerError(
|
|
227
|
+
{ db },
|
|
228
|
+
{
|
|
229
|
+
source: 'query_index',
|
|
230
|
+
handler: 'query_index:build-index-doc:translations',
|
|
231
|
+
error: translationsError,
|
|
232
|
+
entityType: params.entityType,
|
|
233
|
+
recordId: params.recordId,
|
|
234
|
+
tenantId: params.tenantId ?? null,
|
|
235
|
+
organizationId: params.organizationId ?? null,
|
|
236
|
+
},
|
|
237
|
+
).catch((loggingError: unknown) => reportTranslationsFailure(loggingError, 'query_index.translations_log_failed'))
|
|
238
|
+
} finally {
|
|
239
|
+
if (translationsSavepoint) {
|
|
240
|
+
try {
|
|
241
|
+
await sql`RELEASE SAVEPOINT ${sql.id(translationsSavepoint)}`.execute(db)
|
|
242
|
+
} catch (loggingError) {
|
|
243
|
+
await sql`ROLLBACK TO SAVEPOINT ${sql.id(translationsSavepoint)}`.execute(db)
|
|
244
|
+
await sql`RELEASE SAVEPOINT ${sql.id(translationsSavepoint)}`.execute(db)
|
|
245
|
+
reportTranslationsFailure(loggingError, 'query_index.translations_log_failed')
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
206
249
|
|
|
207
250
|
await applyIndexDocEnrichers(db, params.entityType, [{
|
|
208
251
|
recordId: String(params.recordId),
|
|
@@ -63,7 +63,7 @@ export function WarrantyWorkspace({
|
|
|
63
63
|
) : null}
|
|
64
64
|
<div className={cn(
|
|
65
65
|
'min-w-0 [&>[data-component-handle]>div:first-child_[data-slot=button]]:h-8 [&>[data-component-handle]>div:first-child_[data-slot=search-input-wrapper]]:h-8 [&_[data-slot=table-header]_[data-slot=table-row]]:h-9 [&_[data-slot=table-body]_[data-slot=table-row]]:h-16',
|
|
66
|
-
'md:[&_[data-slot=table-head][class*=sticky]]:bg-card md:[&_[data-slot=table-cell][class*=sticky]]:bg-card',
|
|
66
|
+
'[&_[data-slot=table-header]]:bg-card md:[&_[data-slot=table-head][class*=sticky]]:bg-card md:[&_[data-slot=table-cell][class*=sticky]]:bg-card',
|
|
67
67
|
'md:[&_[data-slot=table-head][class*=sticky]]:bg-linear-to-r md:[&_[data-slot=table-head][class*=sticky]]:from-muted/40 md:[&_[data-slot=table-head][class*=sticky]]:to-muted/40',
|
|
68
68
|
'md:[&_[data-slot=table-row]:hover_[data-slot=table-cell][class*=sticky]]:bg-linear-to-r md:[&_[data-slot=table-row]:hover_[data-slot=table-cell][class*=sticky]]:from-muted/30 md:[&_[data-slot=table-row]:hover_[data-slot=table-cell][class*=sticky]]:to-muted/30',
|
|
69
69
|
contentClassName,
|