@open-mercato/shared 0.6.7-develop.6775.1.c2313bb8a3 → 0.6.7-develop.6785.1.1dd7cfac55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +18 -3
- package/dist/lib/ai/safety-identifier.js +32 -0
- package/dist/lib/ai/safety-identifier.js.map +7 -0
- package/dist/lib/commands/command-bus.js +5 -1
- package/dist/lib/commands/command-bus.js.map +2 -2
- package/dist/lib/commands/command-interceptor-runner.js +2 -2
- package/dist/lib/commands/command-interceptor-runner.js.map +2 -2
- package/dist/lib/crud/enricher-runner.js +2 -11
- package/dist/lib/crud/enricher-runner.js.map +2 -2
- package/dist/lib/crud/factory.js +4 -2
- package/dist/lib/crud/factory.js.map +2 -2
- package/dist/lib/crud/interceptor-runner.js +2 -2
- package/dist/lib/crud/interceptor-runner.js.map +2 -2
- package/dist/lib/crud/mutation-guard-registry.js +2 -2
- package/dist/lib/crud/mutation-guard-registry.js.map +2 -2
- package/dist/lib/crud/types.js +4 -0
- package/dist/lib/crud/types.js.map +3 -3
- package/dist/lib/data/consistency.js +19 -0
- package/dist/lib/data/consistency.js.map +7 -0
- package/dist/lib/data/engine.js +37 -11
- package/dist/lib/data/engine.js.map +2 -2
- package/dist/lib/version.js +1 -1
- package/dist/lib/version.js.map +1 -1
- package/dist/security/enabledModulesRegistry.js +56 -11
- package/dist/security/enabledModulesRegistry.js.map +2 -2
- package/dist/security/featurePolicy.js +62 -0
- package/dist/security/featurePolicy.js.map +7 -0
- package/package.json +6 -2
- package/src/lib/ai/__tests__/llm-provider-contract.test.ts +59 -0
- package/src/lib/ai/__tests__/safety-identifier.test.ts +71 -0
- package/src/lib/ai/llm-provider.ts +33 -0
- package/src/lib/ai/safety-identifier.ts +73 -0
- package/src/lib/commands/__tests__/command-interceptor-runner.test.ts +28 -0
- package/src/lib/commands/command-bus.ts +5 -1
- package/src/lib/commands/command-interceptor-runner.ts +2 -2
- package/src/lib/crud/__tests__/crud-factory.test.ts +18 -0
- package/src/lib/crud/__tests__/mutation-guard-registry.test.ts +26 -0
- package/src/lib/crud/enricher-runner.ts +2 -11
- package/src/lib/crud/factory.ts +2 -0
- package/src/lib/crud/interceptor-runner.ts +2 -2
- package/src/lib/crud/mutation-guard-registry.ts +2 -2
- package/src/lib/crud/types.ts +3 -0
- package/src/lib/data/__tests__/consistency.test.ts +38 -0
- package/src/lib/data/__tests__/engine.bulk-suppress.test.ts +18 -3
- package/src/lib/data/consistency.ts +17 -0
- package/src/lib/data/engine.ts +50 -16
- package/src/modules/customer-auth.ts +1 -0
- package/src/modules/navigation/backendChrome.ts +1 -0
- package/src/security/__tests__/featurePolicy.test.ts +166 -0
- package/src/security/enabledModulesRegistry.ts +64 -12
- package/src/security/featurePolicy.ts +89 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
[build:shared] found
|
|
1
|
+
[build:shared] found 251 entry points
|
|
2
2
|
[build:shared] built successfully
|
package/AGENTS.md
CHANGED
|
@@ -186,9 +186,23 @@ The detection scripts (`yarn i18n:check-hardcoded`, `yarn i18n:check-values`) li
|
|
|
186
186
|
import { withScopedPayload, createScopedApiHelpers } from '@open-mercato/shared/lib/api/scoped'
|
|
187
187
|
```
|
|
188
188
|
|
|
189
|
-
### Feature
|
|
189
|
+
### Feature Policy and Matching
|
|
190
190
|
|
|
191
|
-
|
|
191
|
+
Server authorization MUST use the consolidated policy:
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
import {
|
|
195
|
+
authorizeFeatures,
|
|
196
|
+
resolveEffectiveFeatures,
|
|
197
|
+
} from '@open-mercato/shared/security/featurePolicy'
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
- Prefer the realm service (`rbacService.userHasAllFeatures` or `customerRbacService.userHasAllFeatures`) when a user and scope are available.
|
|
201
|
+
- Use `authorizeFeatures(required, subject)` only when the caller already has an ACL snapshot. It owns removed-feature, disabled-module, unrestricted-user, scope, and wildcard ordering.
|
|
202
|
+
- Use `resolveEffectiveFeatures(grants)` for browser capability payloads. It returns concrete IDs and never wildcards.
|
|
203
|
+
- Raw `loadAcl` / `getGrantedFeatures` remain valid for ACL management and infrastructure inspection, not as authorization entrypoints.
|
|
204
|
+
|
|
205
|
+
The low-level helpers remain pure for browser checks over effective projections and isolated grant-matching utilities:
|
|
192
206
|
|
|
193
207
|
```typescript
|
|
194
208
|
import { hasFeature, hasAllFeatures } from '@open-mercato/shared/security/features'
|
|
@@ -196,7 +210,8 @@ import { hasFeature, hasAllFeatures } from '@open-mercato/shared/security/featur
|
|
|
196
210
|
|
|
197
211
|
- Use `hasFeature(granted, 'module.action')` for single-feature checks.
|
|
198
212
|
- Use `hasAllFeatures(granted, required)` for arrays such as `features`, `requireFeatures`, or handler guard lists.
|
|
199
|
-
- MUST NOT
|
|
213
|
+
- MUST NOT use these low-level helpers as server authorization entrypoints.
|
|
214
|
+
- MUST NOT gate feature arrays with `includes(...)`, `Set.has(...)`, or ad hoc `every(...includes(...))` checks.
|
|
200
215
|
|
|
201
216
|
### CRUD HTTP Errors — MUST use the shared helpers instead of hand-rolling `CrudHttpError`
|
|
202
217
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
const SAFETY_IDENTIFIER_SECRET_LABEL = "open-mercato:ai-safety-identifier:v1";
|
|
3
|
+
const derivedSecretCache = /* @__PURE__ */ new Map();
|
|
4
|
+
function readBaseSecret(explicit) {
|
|
5
|
+
const secret = explicit ?? process.env.JWT_SECRET;
|
|
6
|
+
if (!secret) {
|
|
7
|
+
throw new Error("[internal] JWT_SECRET is not set; cannot derive AI safety-identifier secret");
|
|
8
|
+
}
|
|
9
|
+
return secret;
|
|
10
|
+
}
|
|
11
|
+
function deriveAiSafetyIdentifierSecret(baseSecret) {
|
|
12
|
+
const base = readBaseSecret(baseSecret);
|
|
13
|
+
const cached = derivedSecretCache.get(base);
|
|
14
|
+
if (cached !== void 0) return cached;
|
|
15
|
+
const derived = crypto.createHmac("sha256", base).update(SAFETY_IDENTIFIER_SECRET_LABEL).digest("hex");
|
|
16
|
+
derivedSecretCache.set(base, derived);
|
|
17
|
+
return derived;
|
|
18
|
+
}
|
|
19
|
+
function computeEndUserIdentifier(tenantId, userId, options) {
|
|
20
|
+
const normalizedUser = (userId ?? "").trim();
|
|
21
|
+
if (!normalizedUser) {
|
|
22
|
+
throw new Error("[internal] computeEndUserIdentifier requires a non-empty userId");
|
|
23
|
+
}
|
|
24
|
+
const key = deriveAiSafetyIdentifierSecret(options?.baseSecret);
|
|
25
|
+
const salt = (tenantId ?? "").trim();
|
|
26
|
+
return crypto.createHmac("sha256", key).update(`${salt}:${normalizedUser}`).digest("hex");
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
computeEndUserIdentifier,
|
|
30
|
+
deriveAiSafetyIdentifierSecret
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=safety-identifier.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/lib/ai/safety-identifier.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * End-user safety identifiers for AI provider calls.\n *\n * Providers such as OpenAI let developers attach an opaque per-end-user\n * identifier to each request so abuse enforcement can target one user instead\n * of suspending the whole API-key organization. We never send a reversible id:\n * the value is a tenant-salted HMAC computed from the platform's existing auth\n * secret, so no PII or internal id leaves the platform and the same end user in\n * two tenants produces two unrelated hashes.\n *\n * The per-process secret derivation mirrors `deriveJwtAudienceSecret`\n * (`@open-mercato/shared/lib/auth/jwt`): one HMAC from the base `JWT_SECRET`\n * under a versioned purpose label, memoized for the process lifetime. No new\n * secret to provision.\n *\n * @see .ai/specs/2026-06-04-ai-input-moderation-and-safety-identifiers.md\n */\n\nimport crypto from 'node:crypto'\n\nconst SAFETY_IDENTIFIER_SECRET_LABEL = 'open-mercato:ai-safety-identifier:v1'\n\nconst derivedSecretCache = new Map<string, string>()\n\nfunction readBaseSecret(explicit?: string): string {\n const secret = explicit ?? process.env.JWT_SECRET\n if (!secret) {\n throw new Error('[internal] JWT_SECRET is not set; cannot derive AI safety-identifier secret')\n }\n return secret\n}\n\n/**\n * Derive the per-process safety-identifier HMAC key from the base auth secret.\n *\n * Deterministic HMAC-SHA256 of a versioned purpose label keyed by the base\n * secret, memoized per base secret. Rotating the base secret rotates every\n * derived identifier \u2014 documented as accepted (identifiers are advisory\n * provider-side metadata, not an in-platform security control).\n */\nexport function deriveAiSafetyIdentifierSecret(baseSecret?: string): string {\n const base = readBaseSecret(baseSecret)\n const cached = derivedSecretCache.get(base)\n if (cached !== undefined) return cached\n const derived = crypto\n .createHmac('sha256', base)\n .update(SAFETY_IDENTIFIER_SECRET_LABEL)\n .digest('hex')\n derivedSecretCache.set(base, derived)\n return derived\n}\n\n/**\n * Compute the opaque end-user safety identifier for a (tenant, user) pair.\n *\n * Returns a 64-char lowercase hex HMAC-SHA256 of `${tenantId}:${userId}` keyed\n * by the derived secret. Throws (with an `[internal]` message) when the base\n * secret is missing or `userId` is empty \u2014 callers in the runtime wrap this in\n * a best-effort try/catch so identifier-derivation failures never break chat.\n */\nexport function computeEndUserIdentifier(\n tenantId: string | null | undefined,\n userId: string,\n options?: { baseSecret?: string },\n): string {\n const normalizedUser = (userId ?? '').trim()\n if (!normalizedUser) {\n throw new Error('[internal] computeEndUserIdentifier requires a non-empty userId')\n }\n const key = deriveAiSafetyIdentifierSecret(options?.baseSecret)\n const salt = (tenantId ?? '').trim()\n return crypto.createHmac('sha256', key).update(`${salt}:${normalizedUser}`).digest('hex')\n}\n"],
|
|
5
|
+
"mappings": "AAkBA,OAAO,YAAY;AAEnB,MAAM,iCAAiC;AAEvC,MAAM,qBAAqB,oBAAI,IAAoB;AAEnD,SAAS,eAAe,UAA2B;AACjD,QAAM,SAAS,YAAY,QAAQ,IAAI;AACvC,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,6EAA6E;AAAA,EAC/F;AACA,SAAO;AACT;AAUO,SAAS,+BAA+B,YAA6B;AAC1E,QAAM,OAAO,eAAe,UAAU;AACtC,QAAM,SAAS,mBAAmB,IAAI,IAAI;AAC1C,MAAI,WAAW,OAAW,QAAO;AACjC,QAAM,UAAU,OACb,WAAW,UAAU,IAAI,EACzB,OAAO,8BAA8B,EACrC,OAAO,KAAK;AACf,qBAAmB,IAAI,MAAM,OAAO;AACpC,SAAO;AACT;AAUO,SAAS,yBACd,UACA,QACA,SACQ;AACR,QAAM,kBAAkB,UAAU,IAAI,KAAK;AAC3C,MAAI,CAAC,gBAAgB;AACnB,UAAM,IAAI,MAAM,iEAAiE;AAAA,EACnF;AACA,QAAM,MAAM,+BAA+B,SAAS,UAAU;AAC9D,QAAM,QAAQ,YAAY,IAAI,KAAK;AACnC,SAAO,OAAO,WAAW,UAAU,GAAG,EAAE,OAAO,GAAG,IAAI,IAAI,cAAc,EAAE,EAAE,OAAO,KAAK;AAC1F;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
runCommandInterceptorsAfterUndo
|
|
17
17
|
} from "./command-interceptor-runner.js";
|
|
18
18
|
import { CommandInterceptorError } from "./errors.js";
|
|
19
|
+
import { isReadProjectionAlwaysConsistent } from "@open-mercato/shared/lib/data/consistency";
|
|
19
20
|
import { createLogger } from "../logger/index.js";
|
|
20
21
|
const logger = createLogger("shared").child({ component: "commands" });
|
|
21
22
|
const SKIPPED_ACTION_LOG_RESOURCE_KINDS = /* @__PURE__ */ new Set([
|
|
@@ -575,7 +576,10 @@ class CommandBus {
|
|
|
575
576
|
try {
|
|
576
577
|
const dataEngine = container.resolve("dataEngine");
|
|
577
578
|
await dataEngine.flushOrmEntityChanges(suppress);
|
|
578
|
-
} catch {
|
|
579
|
+
} catch (error) {
|
|
580
|
+
if (isReadProjectionAlwaysConsistent()) {
|
|
581
|
+
throw error;
|
|
582
|
+
}
|
|
579
583
|
}
|
|
580
584
|
}
|
|
581
585
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/commands/command-bus.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ActionLog } from '@open-mercato/core/modules/audit_logs/data/entities'\nimport type { ActionLogCreateInput } from '@open-mercato/core/modules/audit_logs/data/validators'\nimport { commandRegistry } from './registry'\nimport type {\n BulkImportSuppression,\n CommandExecutionOptions,\n CommandExecuteResult,\n CommandHandler,\n CommandLogBuilderArgs,\n CommandLogMetadata,\n CommandRuntimeContext,\n} from './types'\nimport { defaultUndoToken } from './types'\nimport type { ActionLogService } from '@open-mercato/core/modules/audit_logs/services/actionLogService'\nimport type { AwilixContainer } from 'awilix'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport {\n canonicalizeResourceTag,\n deriveResourceFromCommandId,\n invalidateCrudCache,\n pickFirstIdentifier,\n isCrudCacheDebugEnabled,\n} from '@open-mercato/shared/lib/crud/cache'\nimport { normalizeCustomFieldKey } from '@open-mercato/shared/lib/custom-fields/keys'\nimport { getAllCommandInterceptorInstances } from './command-interceptor-store'\nimport {\n runCommandInterceptorsBefore,\n runCommandInterceptorsAfter,\n runCommandInterceptorsBeforeUndo,\n runCommandInterceptorsAfterUndo,\n} from './command-interceptor-runner'\nimport type { CommandInterceptorContext } from './command-interceptor'\nimport { CommandInterceptorError } from './errors'\nimport { createLogger } from '../logger'\n\nconst logger = createLogger('shared').child({ component: 'commands' })\n\nconst SKIPPED_ACTION_LOG_RESOURCE_KINDS = new Set<string>([\n 'audit_logs.access',\n 'audit_logs.action',\n 'dashboards.layout',\n 'dashboards.user_widgets',\n 'dashboards.role_widgets',\n])\n\nfunction asRecord(input: unknown): Record<string, unknown> | null {\n if (!input || typeof input !== 'object' || Array.isArray(input)) return null\n return input as Record<string, unknown>\n}\n\n/** Command handlers often return domain keys (e.g. warehouseId) without `id`; cache invalidation must still resolve the record. */\nfunction extractPrimaryIdFromCommandResult(result: unknown): string | null {\n const r = asRecord(result)\n if (!r) return null\n const direct = pickFirstIdentifier(r.id, r.entityId, r.recordId)\n if (direct) return direct\n for (const key of [\n 'warehouseId',\n 'zoneId',\n 'locationId',\n 'lotId',\n 'reservationId',\n 'profileId',\n 'movementId',\n 'balanceId',\n ]) {\n const v = r[key]\n if (typeof v === 'string' && v.trim().length > 0) return v.trim()\n }\n return null\n}\n\nfunction toISOString(value: unknown): string | null {\n if (value instanceof Date) {\n const iso = value.toISOString()\n return Number.isNaN(value.getTime()) ? null : iso\n }\n if (typeof value === 'string') {\n const parsed = new Date(value)\n return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString()\n }\n return null\n}\n\nfunction deepEqual(a: unknown, b: unknown, seen?: Set<unknown>): boolean {\n if (Object.is(a, b)) return true\n if (a instanceof Date || b instanceof Date) {\n const aIso = toISOString(a)\n const bIso = toISOString(b)\n if (aIso != null && bIso != null) return aIso === bIso\n return false\n }\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false\n return a.every((value, index) => deepEqual(value, b[index], seen))\n }\n if (a && b && typeof a === 'object' && typeof b === 'object') {\n if (!seen) seen = new Set()\n if (seen.has(a) || seen.has(b)) return false\n seen.add(a)\n seen.add(b)\n const aRec = a as Record<string, unknown>\n const bRec = b as Record<string, unknown>\n const keysA = Object.keys(aRec)\n const keysB = Object.keys(bRec)\n if (keysA.length !== keysB.length) return false\n return keysA.every((key) => deepEqual(aRec[key], bRec[key], seen))\n }\n return false\n}\n\nconst CUSTOM_FIELD_CONTAINER_KEYS = new Set(['custom', 'customFields', 'customValues', 'cf'])\nconst SKIPPED_CHANGE_KEYS = new Set(['updatedAt', 'updated_at'])\n\nfunction appendCustomFieldChanges(\n changes: Record<string, { from: unknown; to: unknown }>,\n before: unknown,\n after: unknown\n): boolean {\n const beforeRec = asRecord(before)\n const afterRec = asRecord(after)\n if (!beforeRec && !afterRec) return false\n const left = beforeRec ?? {}\n const right = afterRec ?? {}\n const keys = new Set([...Object.keys(left), ...Object.keys(right)])\n for (const key of keys) {\n const from = left[key]\n const to = right[key]\n if (!deepEqual(from, to)) {\n changes[normalizeCustomFieldKey(key)] = { from, to }\n }\n }\n return true\n}\n\nfunction buildRecordChanges(\n before: Record<string, unknown>,\n after: Record<string, unknown>,\n): Record<string, { from: unknown; to: unknown }> {\n return buildRecordChangesDeep(before, after)\n}\n\nfunction buildRecordChangesDeep(\n before: Record<string, unknown>,\n after: Record<string, unknown>,\n prefix?: string,\n seen?: Set<unknown>,\n): Record<string, { from: unknown; to: unknown }> {\n const changes: Record<string, { from: unknown; to: unknown }> = {}\n if (!seen) seen = new Set()\n if (seen.has(before) || seen.has(after)) return changes\n seen.add(before)\n seen.add(after)\n const keys = new Set([...Object.keys(before), ...Object.keys(after)])\n for (const key of keys) {\n if (SKIPPED_CHANGE_KEYS.has(key)) continue\n if (CUSTOM_FIELD_CONTAINER_KEYS.has(key)) {\n const handled = appendCustomFieldChanges(changes, before[key], after[key])\n if (handled) continue\n }\n const from = before[key]\n const to = after[key]\n const path = prefix ? `${prefix}.${key}` : key\n const fromRec = asRecord(from)\n const toRec = asRecord(to)\n if (fromRec && toRec) {\n const nested = buildRecordChangesDeep(fromRec, toRec, path, seen)\n if (Object.keys(nested).length) {\n Object.assign(changes, nested)\n continue\n }\n }\n if (!deepEqual(from, to)) {\n changes[path] = { from, to }\n }\n }\n return changes\n}\n\nfunction deriveChangesFromSnapshots(\n before: unknown,\n after: unknown,\n): Record<string, { from: unknown; to: unknown }> | null {\n const beforeRec = asRecord(before)\n const afterRec = asRecord(after)\n if (!beforeRec || !afterRec) return null\n const changes = buildRecordChanges(beforeRec, afterRec)\n return Object.keys(changes).length ? changes : null\n}\n\nfunction invertRecordedChanges(\n changes: unknown,\n): Record<string, { from: unknown; to: unknown }> | null {\n const source = asRecord(changes)\n if (!source) return null\n const inverted: Record<string, { from: unknown; to: unknown }> = {}\n for (const [key, value] of Object.entries(source)) {\n const entry = asRecord(value)\n if (!entry || (!('from' in entry) && !('to' in entry))) continue\n inverted[key] = {\n from: entry.to,\n to: entry.from,\n }\n }\n return Object.keys(inverted).length ? inverted : null\n}\n\nfunction extractAliasList(source: unknown): string[] {\n if (!source || typeof source !== 'object' || Array.isArray(source)) return []\n const record = source as Record<string, unknown>\n const raw = record.cacheAliases\n if (!Array.isArray(raw)) return []\n const aliases = new Set<string>()\n for (const value of raw) {\n if (typeof value !== 'string') continue\n const normalized = canonicalizeResourceTag(value)\n if (normalized) aliases.add(normalized)\n }\n return Array.from(aliases)\n}\n\nexport class CommandBus {\n async execute<TInput = unknown, TResult = unknown>(\n commandId: string,\n options: CommandExecutionOptions<TInput>\n ): Promise<CommandExecuteResult<TResult>> {\n const handler = await this.resolveHandler<TInput, TResult>(commandId)\n\n // Run beforeExecute command interceptors\n const allInterceptors = getAllCommandInterceptorInstances()\n let interceptorMetadata = new Map<string, Record<string, unknown>>()\n let effectiveOptions = options\n const userFeatures = allInterceptors.length\n ? await this.resolveUserFeaturesForInterceptors(options.ctx)\n : []\n if (allInterceptors.length) {\n const interceptorCtx: CommandInterceptorContext = {\n commandId,\n auth: options.ctx.auth ?? null,\n selectedOrganizationId: options.ctx.selectedOrganizationId ?? options.ctx.auth?.orgId ?? null,\n container: options.ctx.container,\n }\n const beforeResult = await runCommandInterceptorsBefore(\n allInterceptors, commandId, options.input, interceptorCtx, userFeatures,\n )\n if (!beforeResult.ok) {\n throw new CommandInterceptorError(beforeResult.error!.message)\n }\n interceptorMetadata = beforeResult.metadataByInterceptor\n if (beforeResult.modifiedInput) {\n effectiveOptions = {\n ...options,\n input: { ...(options.input as object), ...beforeResult.modifiedInput } as TInput,\n }\n }\n }\n\n const snapshots = await this.prepareSnapshots(handler, effectiveOptions)\n const redoLogEntry = effectiveOptions.redoLogEntry ?? null\n const result =\n redoLogEntry && typeof handler.redo === 'function'\n ? await handler.redo({ input: effectiveOptions.input, ctx: effectiveOptions.ctx, logEntry: redoLogEntry })\n : await handler.execute(effectiveOptions.input, effectiveOptions.ctx)\n const afterSnapshot = await this.captureAfter(handler, effectiveOptions, result)\n const snapshotsWithAfter = { ...snapshots, after: afterSnapshot }\n const logMeta = await this.buildLog(handler, effectiveOptions, result, snapshotsWithAfter)\n let mergedMeta = this.mergeMetadata(effectiveOptions.metadata, logMeta)\n const undoable = this.isUndoable(handler)\n if (undoable) {\n mergedMeta = mergedMeta ?? {}\n if (!mergedMeta.undoToken) mergedMeta.undoToken = defaultUndoToken()\n if (mergedMeta.actorUserId === undefined) mergedMeta.actorUserId = effectiveOptions.ctx.auth?.sub ?? null\n }\n if (afterSnapshot !== undefined && afterSnapshot !== null) {\n if (!mergedMeta) {\n mergedMeta = { snapshotAfter: afterSnapshot }\n } else if (!mergedMeta.snapshotAfter) {\n mergedMeta.snapshotAfter = afterSnapshot\n }\n }\n if (snapshots.before) {\n if (!mergedMeta) {\n mergedMeta = { snapshotBefore: snapshots.before }\n } else if (!mergedMeta.snapshotBefore) {\n mergedMeta.snapshotBefore = snapshots.before\n }\n }\n if (mergedMeta?.snapshotBefore !== undefined && mergedMeta?.snapshotAfter !== undefined) {\n const currentChanges = mergedMeta.changes\n const shouldInfer =\n currentChanges === undefined ||\n currentChanges === null ||\n (typeof currentChanges === 'object' && !Array.isArray(currentChanges) && Object.keys(currentChanges).length === 0)\n if (shouldInfer) {\n const inferred = deriveChangesFromSnapshots(mergedMeta.snapshotBefore, mergedMeta.snapshotAfter)\n if (inferred) mergedMeta.changes = inferred\n }\n }\n const logEntry = await this.persistLog(commandId, effectiveOptions, mergedMeta)\n\n // Run afterExecute command interceptors\n let finalResult = result\n if (allInterceptors.length) {\n const interceptorCtx: CommandInterceptorContext = {\n commandId,\n auth: effectiveOptions.ctx.auth ?? null,\n selectedOrganizationId: effectiveOptions.ctx.selectedOrganizationId ?? effectiveOptions.ctx.auth?.orgId ?? null,\n container: effectiveOptions.ctx.container,\n }\n const afterResult = await runCommandInterceptorsAfter(\n allInterceptors, commandId, effectiveOptions.input, result, interceptorCtx,\n userFeatures, interceptorMetadata,\n )\n if (afterResult.modifiedResult && typeof result === 'object' && result) {\n finalResult = { ...(result as object), ...afterResult.modifiedResult } as Awaited<TResult>\n }\n }\n\n if (!effectiveOptions.skipCacheInvalidation) {\n await this.invalidateCacheAfterExecute(commandId, effectiveOptions, finalResult, mergedMeta)\n }\n // Bulk-import backfills defer heavy per-record side effects: the ctx flags are read here and\n // threaded as a local into the flush (never stored on the shared dataEngine), so a concurrent\n // command with different flags can't observe them. Reindex is restored by the caller's\n // end-of-run `query_index rebuild`. Mirrors `skipCacheInvalidation` above.\n await this.flushCrudSideEffects(effectiveOptions.ctx.container, effectiveOptions.ctx?.bulkImport)\n return { result: finalResult, logEntry }\n }\n\n async undo(undoToken: string, ctx: CommandRuntimeContext): Promise<void> {\n const service = (ctx.container.resolve('actionLogService') as ActionLogService)\n const log = await service.findByUndoToken(undoToken)\n if (!log) throw new Error('Undo token expired or not found')\n const handler = await this.resolveHandler(log.commandId)\n if (!handler.undo || this.isUndoable(handler) === false) {\n throw new Error(`Command ${log.commandId} is not undoable`)\n }\n\n // Atomically claim the action-log row before running any undo side effects.\n // Two concurrent requests holding the same undo token can both pass\n // findByUndoToken/executionState checks; the compare-and-set below ensures\n // only one transitions `done` -> `undoing` and proceeds, the other bails out.\n const claimed = await service.claimForUndo(log.id)\n if (!claimed) throw new Error('Undo token already consumed')\n\n try {\n // Run beforeUndo command interceptors\n const allInterceptors = getAllCommandInterceptorInstances()\n let undoInterceptorMetadata = new Map<string, Record<string, unknown>>()\n const userFeatures = allInterceptors.length\n ? await this.resolveUserFeaturesForInterceptors(ctx)\n : []\n if (allInterceptors.length) {\n const undoCtx = { input: log.commandPayload, logEntry: log, undoToken }\n const interceptorCtx: CommandInterceptorContext = {\n commandId: log.commandId,\n auth: ctx.auth ?? null,\n selectedOrganizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n container: ctx.container,\n }\n const beforeResult = await runCommandInterceptorsBeforeUndo(\n allInterceptors, log.commandId, undoCtx, interceptorCtx, userFeatures,\n )\n if (!beforeResult.ok) {\n throw new CommandInterceptorError(beforeResult.error!.message)\n }\n undoInterceptorMetadata = beforeResult.metadataByInterceptor\n }\n\n await handler.undo({\n input: log.commandPayload as Parameters<NonNullable<typeof handler.undo>>[0]['input'],\n ctx,\n logEntry: log,\n })\n await service.markUndone(log.id, this.buildUndoTraceLog(log, ctx))\n\n // Run afterUndo command interceptors\n if (allInterceptors.length) {\n const undoCtx = { input: log.commandPayload, logEntry: log, undoToken }\n const interceptorCtx: CommandInterceptorContext = {\n commandId: log.commandId,\n auth: ctx.auth ?? null,\n selectedOrganizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n container: ctx.container,\n }\n await runCommandInterceptorsAfterUndo(\n allInterceptors, log.commandId, undoCtx, interceptorCtx,\n userFeatures, undoInterceptorMetadata,\n )\n }\n\n await this.invalidateCacheAfterUndo(log, ctx)\n await this.flushCrudSideEffects(ctx.container)\n } catch (err) {\n // Undo failed after claiming the row \u2014 release the claim so the action\n // remains retryable instead of being stranded in the `undoing` state.\n await service.releaseUndoClaim(log.id).catch(() => {})\n throw err\n }\n }\n\n private buildUndoTraceLog(log: ActionLog, ctx: CommandRuntimeContext): ActionLogCreateInput | undefined {\n const snapshotBefore = log.snapshotAfter ?? null\n const snapshotAfter = log.snapshotBefore ?? null\n const changes =\n deriveChangesFromSnapshots(snapshotBefore, snapshotAfter)\n ?? invertRecordedChanges(log.changesJson)\n ?? undefined\n\n const baseContext = asRecord(log.contextJson) ?? {}\n const context = {\n ...baseContext,\n historyAction: 'undo',\n sourceLogId: log.id,\n sourceCommandId: log.commandId,\n }\n\n return {\n tenantId: log.tenantId ?? ctx.auth?.tenantId ?? null,\n organizationId: log.organizationId ?? ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n actorUserId: ctx.auth?.sub ?? log.actorUserId ?? null,\n commandId: log.commandId,\n actionLabel: log.actionLabel ?? undefined,\n resourceKind: log.resourceKind ?? undefined,\n resourceId: log.resourceId ?? undefined,\n parentResourceKind: log.parentResourceKind ?? null,\n parentResourceId: log.parentResourceId ?? null,\n relatedResourceKind: log.relatedResourceKind ?? null,\n relatedResourceId: log.relatedResourceId ?? null,\n snapshotBefore,\n snapshotAfter,\n changes,\n context,\n }\n }\n\n private async resolveUserFeaturesForInterceptors(ctx: CommandRuntimeContext): Promise<string[]> {\n if (!ctx.auth) return []\n try {\n type RbacLike = { getGrantedFeatures: (userId: string, opts: { tenantId: string | null; organizationId: string | null }) => Promise<string[]> }\n const rbac = ctx.container.resolve('rbacService') as RbacLike | undefined\n if (rbac?.getGrantedFeatures) {\n return await rbac.getGrantedFeatures(ctx.auth.sub, {\n tenantId: ctx.auth.tenantId,\n organizationId: ctx.selectedOrganizationId ?? ctx.auth.orgId,\n })\n }\n } catch {\n // Intentional: rbacService is not registered in all runtime contexts (CLI, tests, bootstrap).\n // Falling through to return [] is safe \u2014 interceptors without feature gating still run.\n }\n return []\n }\n\n private async resolveHandler<TInput, TResult>(commandId: string): Promise<CommandHandler<TInput, TResult>> {\n const handler =\n commandRegistry.get<TInput, TResult>(commandId) ??\n ((await commandRegistry.load(commandId)) as CommandHandler<TInput, TResult> | null)\n if (!handler) {\n const moduleName = commandId.split('.')[0]\n const registered = commandRegistry.list()\n const sameModule = registered.filter((id) => id.split('.')[0] === moduleName)\n const registeredLoaders = commandRegistry.listLoaders()\n const sameModuleLoaders = registeredLoaders.filter((id) => id === commandId || id.startsWith(`${moduleName}:`))\n const hint = sameModule.length > 0\n ? ` Registered commands for module \"${moduleName}\": [${sameModule.join(', ')}].`\n : sameModuleLoaders.length > 0\n ? ` Command loaders for module \"${moduleName}\" were registered but none loaded \"${commandId}\".`\n : ` No commands or command loaders registered for module \"${moduleName}\". Ensure the command file is imported or generated lazy command loaders are registered.`\n throw new Error(`Command handler not registered for id ${commandId}.${hint}`)\n }\n return handler\n }\n\n private async prepareSnapshots<TInput, TResult>(\n handler: CommandHandler<TInput, TResult>,\n options: CommandExecutionOptions<TInput>\n ): Promise<{ before?: unknown }> {\n if (!handler.prepare) return {}\n try {\n return (await handler.prepare(options.input, options.ctx)) || {}\n } catch (err) {\n throw err\n }\n }\n\n private async captureAfter<TInput, TResult>(\n handler: CommandHandler<TInput, TResult>,\n options: CommandExecutionOptions<TInput>,\n result: TResult\n ): Promise<unknown> {\n if (!handler.captureAfter) return undefined\n return handler.captureAfter(options.input, result, options.ctx)\n }\n\n private async buildLog<TInput, TResult>(\n handler: CommandHandler<TInput, TResult>,\n options: CommandExecutionOptions<TInput>,\n result: TResult,\n snapshots: { before?: unknown; after?: unknown }\n ): Promise<CommandLogMetadata | null> {\n if (!handler.buildLog) return null\n const args: CommandLogBuilderArgs<TInput, TResult> = {\n input: options.input,\n result,\n ctx: options.ctx,\n snapshots,\n }\n return (await handler.buildLog(args)) || null\n }\n\n private mergeMetadata(primary?: CommandLogMetadata | null, secondary?: CommandLogMetadata | null): CommandLogMetadata | null {\n if (!primary && !secondary) return null\n return {\n skipLog: secondary?.skipLog ?? primary?.skipLog ?? false,\n tenantId: secondary?.tenantId ?? primary?.tenantId ?? null,\n organizationId: secondary?.organizationId ?? primary?.organizationId ?? null,\n actorUserId: secondary?.actorUserId ?? primary?.actorUserId ?? null,\n actionLabel: secondary?.actionLabel ?? primary?.actionLabel ?? null,\n resourceKind: secondary?.resourceKind ?? primary?.resourceKind ?? null,\n resourceId: secondary?.resourceId ?? primary?.resourceId ?? null,\n parentResourceKind: secondary?.parentResourceKind ?? primary?.parentResourceKind ?? null,\n parentResourceId: secondary?.parentResourceId ?? primary?.parentResourceId ?? null,\n relatedResourceKind: secondary?.relatedResourceKind ?? primary?.relatedResourceKind ?? null,\n relatedResourceId: secondary?.relatedResourceId ?? primary?.relatedResourceId ?? null,\n undoToken: secondary?.undoToken ?? primary?.undoToken ?? null,\n payload: secondary?.payload ?? primary?.payload ?? null,\n snapshotBefore: secondary?.snapshotBefore ?? primary?.snapshotBefore ?? null,\n snapshotAfter: secondary?.snapshotAfter ?? primary?.snapshotAfter ?? null,\n changes: secondary?.changes ?? primary?.changes ?? null,\n context: secondary?.context ?? primary?.context ?? null,\n }\n }\n\n private async persistLog<TInput>(\n commandId: string,\n options: CommandExecutionOptions<TInput>,\n metadata: CommandLogMetadata | null\n ): Promise<ActionLog | null> {\n if (!metadata) return null\n if (metadata.skipLog) return null\n const resourceKind =\n typeof metadata.resourceKind === 'string' ? metadata.resourceKind : null\n if (resourceKind && SKIPPED_ACTION_LOG_RESOURCE_KINDS.has(resourceKind)) {\n return null\n }\n let service: ActionLogService | null = null\n try {\n service = (options.ctx.container.resolve('actionLogService') as ActionLogService)\n } catch {\n service = null\n }\n if (!service) return null\n\n const tenantId = metadata.tenantId ?? options.ctx.auth?.tenantId ?? null\n const organizationId =\n metadata.organizationId ?? options.ctx.selectedOrganizationId ?? options.ctx.auth?.orgId ?? null\n const actorUserId = metadata.actorUserId ?? options.ctx.auth?.sub ?? null\n const payload: Record<string, unknown> = {\n tenantId: tenantId ?? undefined,\n organizationId: organizationId ?? undefined,\n actorUserId: actorUserId ?? undefined,\n commandId,\n }\n\n if (metadata) {\n if ('actionLabel' in metadata && metadata.actionLabel != null) payload.actionLabel = metadata.actionLabel\n if ('resourceKind' in metadata && metadata.resourceKind != null) payload.resourceKind = metadata.resourceKind\n if ('resourceId' in metadata && metadata.resourceId != null) payload.resourceId = metadata.resourceId\n if ('parentResourceKind' in metadata && metadata.parentResourceKind != null) payload.parentResourceKind = metadata.parentResourceKind\n if ('parentResourceId' in metadata && metadata.parentResourceId != null) payload.parentResourceId = metadata.parentResourceId\n if ('relatedResourceKind' in metadata && metadata.relatedResourceKind != null) payload.relatedResourceKind = metadata.relatedResourceKind\n if ('relatedResourceId' in metadata && metadata.relatedResourceId != null) payload.relatedResourceId = metadata.relatedResourceId\n if ('undoToken' in metadata && metadata.undoToken != null) payload.undoToken = metadata.undoToken\n if ('payload' in metadata && metadata.payload !== undefined) payload.commandPayload = metadata.payload\n if ('snapshotBefore' in metadata && metadata.snapshotBefore !== undefined) payload.snapshotBefore = metadata.snapshotBefore\n if ('snapshotAfter' in metadata && metadata.snapshotAfter !== undefined) payload.snapshotAfter = metadata.snapshotAfter\n if ('changes' in metadata && metadata.changes !== undefined && metadata.changes !== null) payload.changes = metadata.changes\n if ('context' in metadata && metadata.context !== undefined && metadata.context !== null) payload.context = metadata.context\n }\n\n const redoEnvelope = wrapRedoPayload('commandPayload' in payload ? (payload.commandPayload as unknown) : undefined, options.input)\n payload.commandPayload = redoEnvelope\n\n return await service.log(payload as ActionLogCreateInput)\n }\n\n private isUndoable(handler: CommandHandler<unknown, unknown>): boolean {\n return handler.isUndoable !== false && typeof handler.undo === 'function'\n }\n\n private async invalidateCacheAfterExecute<TResult>(\n commandId: string,\n options: CommandExecutionOptions<unknown>,\n result: TResult,\n metadata: CommandLogMetadata | null\n ): Promise<void> {\n const resource = typeof metadata?.resourceKind === 'string' ? metadata.resourceKind : null\n if (!resource) return\n try {\n const ctx = options.ctx\n const resultRecord = asRecord(result)\n const resultEntity = asRecord(resultRecord?.entity)\n const inputRecord = asRecord(options.input)\n const inputEntity = asRecord(inputRecord?.entity)\n\n const recordId = pickFirstIdentifier(\n metadata?.resourceId,\n extractPrimaryIdFromCommandResult(result),\n resultRecord?.entityId,\n resultRecord?.id,\n resultRecord?.recordId,\n resultEntity?.id,\n inputRecord?.id,\n inputRecord?.entityId,\n inputRecord?.recordId,\n inputEntity?.id\n )\n\n const organizationId = pickFirstIdentifier(\n metadata?.organizationId,\n resultRecord?.organizationId,\n resultEntity?.organizationId,\n inputRecord?.organizationId,\n inputEntity?.organizationId,\n ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null\n )\n\n const tenantId = pickFirstIdentifier(\n metadata?.tenantId,\n resultRecord?.tenantId,\n resultEntity?.tenantId,\n inputRecord?.tenantId,\n inputEntity?.tenantId,\n ctx.auth?.tenantId ?? null\n )\n\n const fallbackTenant = pickFirstIdentifier(metadata?.tenantId, ctx.auth?.tenantId ?? null)\n\n const aliasSet = new Set<string>()\n for (const alias of extractAliasList(metadata?.context ?? null)) {\n aliasSet.add(alias)\n }\n const derived = deriveResourceFromCommandId(commandId)\n if (derived) aliasSet.add(derived)\n const aliasExtras = Array.from(aliasSet)\n await invalidateCrudCache(\n ctx.container,\n resource,\n { id: recordId, organizationId, tenantId },\n fallbackTenant,\n `command:${commandId}:execute`,\n aliasExtras\n )\n } catch (err) {\n if (isCrudCacheDebugEnabled()) {\n try {\n logger.debug('Cache execute-invalidation failed', { commandId, err })\n } catch {}\n }\n }\n }\n\n private async invalidateCacheAfterUndo(log: ActionLog, ctx: CommandRuntimeContext): Promise<void> {\n const resource = typeof log.resourceKind === 'string' ? log.resourceKind : null\n if (!resource) return\n try {\n const recordId = pickFirstIdentifier(log.resourceId)\n const organizationId = pickFirstIdentifier(log.organizationId, ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null)\n const tenantId = pickFirstIdentifier(log.tenantId, ctx.auth?.tenantId ?? null)\n const fallbackTenant = pickFirstIdentifier(log.tenantId, ctx.auth?.tenantId ?? null)\n const aliasSet = new Set<string>()\n for (const alias of extractAliasList(log.contextJson ?? null)) {\n aliasSet.add(alias)\n }\n const derived = deriveResourceFromCommandId(log.commandId)\n if (derived) aliasSet.add(derived)\n const aliasExtras = Array.from(aliasSet)\n await invalidateCrudCache(\n ctx.container,\n resource,\n { id: recordId, organizationId, tenantId },\n fallbackTenant,\n `command:${log.commandId}:undo`,\n aliasExtras\n )\n } catch (err) {\n if (isCrudCacheDebugEnabled()) {\n try {\n logger.debug('Cache undo-invalidation failed', { commandId: log.commandId, err })\n } catch {}\n }\n }\n }\n\n private async flushCrudSideEffects(container: AwilixContainer, suppress?: BulkImportSuppression): Promise<void> {\n try {\n const dataEngine = (container.resolve('dataEngine') as DataEngine)\n await dataEngine.flushOrmEntityChanges(suppress)\n } catch {\n // best-effort: failures should not block command execution\n }\n }\n}\n\ntype RedoEnvelope = {\n __redoInput: unknown\n [key: string]: unknown\n}\n\nfunction wrapRedoPayload(existing: unknown, input: unknown): RedoEnvelope {\n if (!existing || typeof existing !== 'object' || Array.isArray(existing)) {\n const envelope: RedoEnvelope = { __redoInput: input }\n if (existing !== undefined) envelope.value = existing\n return envelope\n }\n const current = existing as Record<string, unknown>\n if ('__redoInput' in current && current.__redoInput !== undefined) {\n return current as RedoEnvelope\n }\n return { __redoInput: input, ...current }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import type { ActionLog } from '@open-mercato/core/modules/audit_logs/data/entities'\nimport type { ActionLogCreateInput } from '@open-mercato/core/modules/audit_logs/data/validators'\nimport { commandRegistry } from './registry'\nimport type {\n BulkImportSuppression,\n CommandExecutionOptions,\n CommandExecuteResult,\n CommandHandler,\n CommandLogBuilderArgs,\n CommandLogMetadata,\n CommandRuntimeContext,\n} from './types'\nimport { defaultUndoToken } from './types'\nimport type { ActionLogService } from '@open-mercato/core/modules/audit_logs/services/actionLogService'\nimport type { AwilixContainer } from 'awilix'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport {\n canonicalizeResourceTag,\n deriveResourceFromCommandId,\n invalidateCrudCache,\n pickFirstIdentifier,\n isCrudCacheDebugEnabled,\n} from '@open-mercato/shared/lib/crud/cache'\nimport { normalizeCustomFieldKey } from '@open-mercato/shared/lib/custom-fields/keys'\nimport { getAllCommandInterceptorInstances } from './command-interceptor-store'\nimport {\n runCommandInterceptorsBefore,\n runCommandInterceptorsAfter,\n runCommandInterceptorsBeforeUndo,\n runCommandInterceptorsAfterUndo,\n} from './command-interceptor-runner'\nimport type { CommandInterceptorContext } from './command-interceptor'\nimport { CommandInterceptorError } from './errors'\nimport { isReadProjectionAlwaysConsistent } from '@open-mercato/shared/lib/data/consistency'\nimport { createLogger } from '../logger'\n\nconst logger = createLogger('shared').child({ component: 'commands' })\n\nconst SKIPPED_ACTION_LOG_RESOURCE_KINDS = new Set<string>([\n 'audit_logs.access',\n 'audit_logs.action',\n 'dashboards.layout',\n 'dashboards.user_widgets',\n 'dashboards.role_widgets',\n])\n\nfunction asRecord(input: unknown): Record<string, unknown> | null {\n if (!input || typeof input !== 'object' || Array.isArray(input)) return null\n return input as Record<string, unknown>\n}\n\n/** Command handlers often return domain keys (e.g. warehouseId) without `id`; cache invalidation must still resolve the record. */\nfunction extractPrimaryIdFromCommandResult(result: unknown): string | null {\n const r = asRecord(result)\n if (!r) return null\n const direct = pickFirstIdentifier(r.id, r.entityId, r.recordId)\n if (direct) return direct\n for (const key of [\n 'warehouseId',\n 'zoneId',\n 'locationId',\n 'lotId',\n 'reservationId',\n 'profileId',\n 'movementId',\n 'balanceId',\n ]) {\n const v = r[key]\n if (typeof v === 'string' && v.trim().length > 0) return v.trim()\n }\n return null\n}\n\nfunction toISOString(value: unknown): string | null {\n if (value instanceof Date) {\n const iso = value.toISOString()\n return Number.isNaN(value.getTime()) ? null : iso\n }\n if (typeof value === 'string') {\n const parsed = new Date(value)\n return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString()\n }\n return null\n}\n\nfunction deepEqual(a: unknown, b: unknown, seen?: Set<unknown>): boolean {\n if (Object.is(a, b)) return true\n if (a instanceof Date || b instanceof Date) {\n const aIso = toISOString(a)\n const bIso = toISOString(b)\n if (aIso != null && bIso != null) return aIso === bIso\n return false\n }\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false\n return a.every((value, index) => deepEqual(value, b[index], seen))\n }\n if (a && b && typeof a === 'object' && typeof b === 'object') {\n if (!seen) seen = new Set()\n if (seen.has(a) || seen.has(b)) return false\n seen.add(a)\n seen.add(b)\n const aRec = a as Record<string, unknown>\n const bRec = b as Record<string, unknown>\n const keysA = Object.keys(aRec)\n const keysB = Object.keys(bRec)\n if (keysA.length !== keysB.length) return false\n return keysA.every((key) => deepEqual(aRec[key], bRec[key], seen))\n }\n return false\n}\n\nconst CUSTOM_FIELD_CONTAINER_KEYS = new Set(['custom', 'customFields', 'customValues', 'cf'])\nconst SKIPPED_CHANGE_KEYS = new Set(['updatedAt', 'updated_at'])\n\nfunction appendCustomFieldChanges(\n changes: Record<string, { from: unknown; to: unknown }>,\n before: unknown,\n after: unknown\n): boolean {\n const beforeRec = asRecord(before)\n const afterRec = asRecord(after)\n if (!beforeRec && !afterRec) return false\n const left = beforeRec ?? {}\n const right = afterRec ?? {}\n const keys = new Set([...Object.keys(left), ...Object.keys(right)])\n for (const key of keys) {\n const from = left[key]\n const to = right[key]\n if (!deepEqual(from, to)) {\n changes[normalizeCustomFieldKey(key)] = { from, to }\n }\n }\n return true\n}\n\nfunction buildRecordChanges(\n before: Record<string, unknown>,\n after: Record<string, unknown>,\n): Record<string, { from: unknown; to: unknown }> {\n return buildRecordChangesDeep(before, after)\n}\n\nfunction buildRecordChangesDeep(\n before: Record<string, unknown>,\n after: Record<string, unknown>,\n prefix?: string,\n seen?: Set<unknown>,\n): Record<string, { from: unknown; to: unknown }> {\n const changes: Record<string, { from: unknown; to: unknown }> = {}\n if (!seen) seen = new Set()\n if (seen.has(before) || seen.has(after)) return changes\n seen.add(before)\n seen.add(after)\n const keys = new Set([...Object.keys(before), ...Object.keys(after)])\n for (const key of keys) {\n if (SKIPPED_CHANGE_KEYS.has(key)) continue\n if (CUSTOM_FIELD_CONTAINER_KEYS.has(key)) {\n const handled = appendCustomFieldChanges(changes, before[key], after[key])\n if (handled) continue\n }\n const from = before[key]\n const to = after[key]\n const path = prefix ? `${prefix}.${key}` : key\n const fromRec = asRecord(from)\n const toRec = asRecord(to)\n if (fromRec && toRec) {\n const nested = buildRecordChangesDeep(fromRec, toRec, path, seen)\n if (Object.keys(nested).length) {\n Object.assign(changes, nested)\n continue\n }\n }\n if (!deepEqual(from, to)) {\n changes[path] = { from, to }\n }\n }\n return changes\n}\n\nfunction deriveChangesFromSnapshots(\n before: unknown,\n after: unknown,\n): Record<string, { from: unknown; to: unknown }> | null {\n const beforeRec = asRecord(before)\n const afterRec = asRecord(after)\n if (!beforeRec || !afterRec) return null\n const changes = buildRecordChanges(beforeRec, afterRec)\n return Object.keys(changes).length ? changes : null\n}\n\nfunction invertRecordedChanges(\n changes: unknown,\n): Record<string, { from: unknown; to: unknown }> | null {\n const source = asRecord(changes)\n if (!source) return null\n const inverted: Record<string, { from: unknown; to: unknown }> = {}\n for (const [key, value] of Object.entries(source)) {\n const entry = asRecord(value)\n if (!entry || (!('from' in entry) && !('to' in entry))) continue\n inverted[key] = {\n from: entry.to,\n to: entry.from,\n }\n }\n return Object.keys(inverted).length ? inverted : null\n}\n\nfunction extractAliasList(source: unknown): string[] {\n if (!source || typeof source !== 'object' || Array.isArray(source)) return []\n const record = source as Record<string, unknown>\n const raw = record.cacheAliases\n if (!Array.isArray(raw)) return []\n const aliases = new Set<string>()\n for (const value of raw) {\n if (typeof value !== 'string') continue\n const normalized = canonicalizeResourceTag(value)\n if (normalized) aliases.add(normalized)\n }\n return Array.from(aliases)\n}\n\nexport class CommandBus {\n async execute<TInput = unknown, TResult = unknown>(\n commandId: string,\n options: CommandExecutionOptions<TInput>\n ): Promise<CommandExecuteResult<TResult>> {\n const handler = await this.resolveHandler<TInput, TResult>(commandId)\n\n // Run beforeExecute command interceptors\n const allInterceptors = getAllCommandInterceptorInstances()\n let interceptorMetadata = new Map<string, Record<string, unknown>>()\n let effectiveOptions = options\n const userFeatures = allInterceptors.length\n ? await this.resolveUserFeaturesForInterceptors(options.ctx)\n : []\n if (allInterceptors.length) {\n const interceptorCtx: CommandInterceptorContext = {\n commandId,\n auth: options.ctx.auth ?? null,\n selectedOrganizationId: options.ctx.selectedOrganizationId ?? options.ctx.auth?.orgId ?? null,\n container: options.ctx.container,\n }\n const beforeResult = await runCommandInterceptorsBefore(\n allInterceptors, commandId, options.input, interceptorCtx, userFeatures,\n )\n if (!beforeResult.ok) {\n throw new CommandInterceptorError(beforeResult.error!.message)\n }\n interceptorMetadata = beforeResult.metadataByInterceptor\n if (beforeResult.modifiedInput) {\n effectiveOptions = {\n ...options,\n input: { ...(options.input as object), ...beforeResult.modifiedInput } as TInput,\n }\n }\n }\n\n const snapshots = await this.prepareSnapshots(handler, effectiveOptions)\n const redoLogEntry = effectiveOptions.redoLogEntry ?? null\n const result =\n redoLogEntry && typeof handler.redo === 'function'\n ? await handler.redo({ input: effectiveOptions.input, ctx: effectiveOptions.ctx, logEntry: redoLogEntry })\n : await handler.execute(effectiveOptions.input, effectiveOptions.ctx)\n const afterSnapshot = await this.captureAfter(handler, effectiveOptions, result)\n const snapshotsWithAfter = { ...snapshots, after: afterSnapshot }\n const logMeta = await this.buildLog(handler, effectiveOptions, result, snapshotsWithAfter)\n let mergedMeta = this.mergeMetadata(effectiveOptions.metadata, logMeta)\n const undoable = this.isUndoable(handler)\n if (undoable) {\n mergedMeta = mergedMeta ?? {}\n if (!mergedMeta.undoToken) mergedMeta.undoToken = defaultUndoToken()\n if (mergedMeta.actorUserId === undefined) mergedMeta.actorUserId = effectiveOptions.ctx.auth?.sub ?? null\n }\n if (afterSnapshot !== undefined && afterSnapshot !== null) {\n if (!mergedMeta) {\n mergedMeta = { snapshotAfter: afterSnapshot }\n } else if (!mergedMeta.snapshotAfter) {\n mergedMeta.snapshotAfter = afterSnapshot\n }\n }\n if (snapshots.before) {\n if (!mergedMeta) {\n mergedMeta = { snapshotBefore: snapshots.before }\n } else if (!mergedMeta.snapshotBefore) {\n mergedMeta.snapshotBefore = snapshots.before\n }\n }\n if (mergedMeta?.snapshotBefore !== undefined && mergedMeta?.snapshotAfter !== undefined) {\n const currentChanges = mergedMeta.changes\n const shouldInfer =\n currentChanges === undefined ||\n currentChanges === null ||\n (typeof currentChanges === 'object' && !Array.isArray(currentChanges) && Object.keys(currentChanges).length === 0)\n if (shouldInfer) {\n const inferred = deriveChangesFromSnapshots(mergedMeta.snapshotBefore, mergedMeta.snapshotAfter)\n if (inferred) mergedMeta.changes = inferred\n }\n }\n const logEntry = await this.persistLog(commandId, effectiveOptions, mergedMeta)\n\n // Run afterExecute command interceptors\n let finalResult = result\n if (allInterceptors.length) {\n const interceptorCtx: CommandInterceptorContext = {\n commandId,\n auth: effectiveOptions.ctx.auth ?? null,\n selectedOrganizationId: effectiveOptions.ctx.selectedOrganizationId ?? effectiveOptions.ctx.auth?.orgId ?? null,\n container: effectiveOptions.ctx.container,\n }\n const afterResult = await runCommandInterceptorsAfter(\n allInterceptors, commandId, effectiveOptions.input, result, interceptorCtx,\n userFeatures, interceptorMetadata,\n )\n if (afterResult.modifiedResult && typeof result === 'object' && result) {\n finalResult = { ...(result as object), ...afterResult.modifiedResult } as Awaited<TResult>\n }\n }\n\n if (!effectiveOptions.skipCacheInvalidation) {\n await this.invalidateCacheAfterExecute(commandId, effectiveOptions, finalResult, mergedMeta)\n }\n // Bulk-import backfills defer heavy per-record side effects: the ctx flags are read here and\n // threaded as a local into the flush (never stored on the shared dataEngine), so a concurrent\n // command with different flags can't observe them. Reindex is restored by the caller's\n // end-of-run `query_index rebuild`. Mirrors `skipCacheInvalidation` above.\n await this.flushCrudSideEffects(effectiveOptions.ctx.container, effectiveOptions.ctx?.bulkImport)\n return { result: finalResult, logEntry }\n }\n\n async undo(undoToken: string, ctx: CommandRuntimeContext): Promise<void> {\n const service = (ctx.container.resolve('actionLogService') as ActionLogService)\n const log = await service.findByUndoToken(undoToken)\n if (!log) throw new Error('Undo token expired or not found')\n const handler = await this.resolveHandler(log.commandId)\n if (!handler.undo || this.isUndoable(handler) === false) {\n throw new Error(`Command ${log.commandId} is not undoable`)\n }\n\n // Atomically claim the action-log row before running any undo side effects.\n // Two concurrent requests holding the same undo token can both pass\n // findByUndoToken/executionState checks; the compare-and-set below ensures\n // only one transitions `done` -> `undoing` and proceeds, the other bails out.\n const claimed = await service.claimForUndo(log.id)\n if (!claimed) throw new Error('Undo token already consumed')\n\n try {\n // Run beforeUndo command interceptors\n const allInterceptors = getAllCommandInterceptorInstances()\n let undoInterceptorMetadata = new Map<string, Record<string, unknown>>()\n const userFeatures = allInterceptors.length\n ? await this.resolveUserFeaturesForInterceptors(ctx)\n : []\n if (allInterceptors.length) {\n const undoCtx = { input: log.commandPayload, logEntry: log, undoToken }\n const interceptorCtx: CommandInterceptorContext = {\n commandId: log.commandId,\n auth: ctx.auth ?? null,\n selectedOrganizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n container: ctx.container,\n }\n const beforeResult = await runCommandInterceptorsBeforeUndo(\n allInterceptors, log.commandId, undoCtx, interceptorCtx, userFeatures,\n )\n if (!beforeResult.ok) {\n throw new CommandInterceptorError(beforeResult.error!.message)\n }\n undoInterceptorMetadata = beforeResult.metadataByInterceptor\n }\n\n await handler.undo({\n input: log.commandPayload as Parameters<NonNullable<typeof handler.undo>>[0]['input'],\n ctx,\n logEntry: log,\n })\n await service.markUndone(log.id, this.buildUndoTraceLog(log, ctx))\n\n // Run afterUndo command interceptors\n if (allInterceptors.length) {\n const undoCtx = { input: log.commandPayload, logEntry: log, undoToken }\n const interceptorCtx: CommandInterceptorContext = {\n commandId: log.commandId,\n auth: ctx.auth ?? null,\n selectedOrganizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n container: ctx.container,\n }\n await runCommandInterceptorsAfterUndo(\n allInterceptors, log.commandId, undoCtx, interceptorCtx,\n userFeatures, undoInterceptorMetadata,\n )\n }\n\n await this.invalidateCacheAfterUndo(log, ctx)\n await this.flushCrudSideEffects(ctx.container)\n } catch (err) {\n // Undo failed after claiming the row \u2014 release the claim so the action\n // remains retryable instead of being stranded in the `undoing` state.\n await service.releaseUndoClaim(log.id).catch(() => {})\n throw err\n }\n }\n\n private buildUndoTraceLog(log: ActionLog, ctx: CommandRuntimeContext): ActionLogCreateInput | undefined {\n const snapshotBefore = log.snapshotAfter ?? null\n const snapshotAfter = log.snapshotBefore ?? null\n const changes =\n deriveChangesFromSnapshots(snapshotBefore, snapshotAfter)\n ?? invertRecordedChanges(log.changesJson)\n ?? undefined\n\n const baseContext = asRecord(log.contextJson) ?? {}\n const context = {\n ...baseContext,\n historyAction: 'undo',\n sourceLogId: log.id,\n sourceCommandId: log.commandId,\n }\n\n return {\n tenantId: log.tenantId ?? ctx.auth?.tenantId ?? null,\n organizationId: log.organizationId ?? ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,\n actorUserId: ctx.auth?.sub ?? log.actorUserId ?? null,\n commandId: log.commandId,\n actionLabel: log.actionLabel ?? undefined,\n resourceKind: log.resourceKind ?? undefined,\n resourceId: log.resourceId ?? undefined,\n parentResourceKind: log.parentResourceKind ?? null,\n parentResourceId: log.parentResourceId ?? null,\n relatedResourceKind: log.relatedResourceKind ?? null,\n relatedResourceId: log.relatedResourceId ?? null,\n snapshotBefore,\n snapshotAfter,\n changes,\n context,\n }\n }\n\n private async resolveUserFeaturesForInterceptors(ctx: CommandRuntimeContext): Promise<string[]> {\n if (!ctx.auth) return []\n try {\n type RbacLike = { getGrantedFeatures: (userId: string, opts: { tenantId: string | null; organizationId: string | null }) => Promise<string[]> }\n const rbac = ctx.container.resolve('rbacService') as RbacLike | undefined\n if (rbac?.getGrantedFeatures) {\n return await rbac.getGrantedFeatures(ctx.auth.sub, {\n tenantId: ctx.auth.tenantId,\n organizationId: ctx.selectedOrganizationId ?? ctx.auth.orgId,\n })\n }\n } catch {\n // Intentional: rbacService is not registered in all runtime contexts (CLI, tests, bootstrap).\n // Falling through to return [] is safe \u2014 interceptors without feature gating still run.\n }\n return []\n }\n\n private async resolveHandler<TInput, TResult>(commandId: string): Promise<CommandHandler<TInput, TResult>> {\n const handler =\n commandRegistry.get<TInput, TResult>(commandId) ??\n ((await commandRegistry.load(commandId)) as CommandHandler<TInput, TResult> | null)\n if (!handler) {\n const moduleName = commandId.split('.')[0]\n const registered = commandRegistry.list()\n const sameModule = registered.filter((id) => id.split('.')[0] === moduleName)\n const registeredLoaders = commandRegistry.listLoaders()\n const sameModuleLoaders = registeredLoaders.filter((id) => id === commandId || id.startsWith(`${moduleName}:`))\n const hint = sameModule.length > 0\n ? ` Registered commands for module \"${moduleName}\": [${sameModule.join(', ')}].`\n : sameModuleLoaders.length > 0\n ? ` Command loaders for module \"${moduleName}\" were registered but none loaded \"${commandId}\".`\n : ` No commands or command loaders registered for module \"${moduleName}\". Ensure the command file is imported or generated lazy command loaders are registered.`\n throw new Error(`Command handler not registered for id ${commandId}.${hint}`)\n }\n return handler\n }\n\n private async prepareSnapshots<TInput, TResult>(\n handler: CommandHandler<TInput, TResult>,\n options: CommandExecutionOptions<TInput>\n ): Promise<{ before?: unknown }> {\n if (!handler.prepare) return {}\n try {\n return (await handler.prepare(options.input, options.ctx)) || {}\n } catch (err) {\n throw err\n }\n }\n\n private async captureAfter<TInput, TResult>(\n handler: CommandHandler<TInput, TResult>,\n options: CommandExecutionOptions<TInput>,\n result: TResult\n ): Promise<unknown> {\n if (!handler.captureAfter) return undefined\n return handler.captureAfter(options.input, result, options.ctx)\n }\n\n private async buildLog<TInput, TResult>(\n handler: CommandHandler<TInput, TResult>,\n options: CommandExecutionOptions<TInput>,\n result: TResult,\n snapshots: { before?: unknown; after?: unknown }\n ): Promise<CommandLogMetadata | null> {\n if (!handler.buildLog) return null\n const args: CommandLogBuilderArgs<TInput, TResult> = {\n input: options.input,\n result,\n ctx: options.ctx,\n snapshots,\n }\n return (await handler.buildLog(args)) || null\n }\n\n private mergeMetadata(primary?: CommandLogMetadata | null, secondary?: CommandLogMetadata | null): CommandLogMetadata | null {\n if (!primary && !secondary) return null\n return {\n skipLog: secondary?.skipLog ?? primary?.skipLog ?? false,\n tenantId: secondary?.tenantId ?? primary?.tenantId ?? null,\n organizationId: secondary?.organizationId ?? primary?.organizationId ?? null,\n actorUserId: secondary?.actorUserId ?? primary?.actorUserId ?? null,\n actionLabel: secondary?.actionLabel ?? primary?.actionLabel ?? null,\n resourceKind: secondary?.resourceKind ?? primary?.resourceKind ?? null,\n resourceId: secondary?.resourceId ?? primary?.resourceId ?? null,\n parentResourceKind: secondary?.parentResourceKind ?? primary?.parentResourceKind ?? null,\n parentResourceId: secondary?.parentResourceId ?? primary?.parentResourceId ?? null,\n relatedResourceKind: secondary?.relatedResourceKind ?? primary?.relatedResourceKind ?? null,\n relatedResourceId: secondary?.relatedResourceId ?? primary?.relatedResourceId ?? null,\n undoToken: secondary?.undoToken ?? primary?.undoToken ?? null,\n payload: secondary?.payload ?? primary?.payload ?? null,\n snapshotBefore: secondary?.snapshotBefore ?? primary?.snapshotBefore ?? null,\n snapshotAfter: secondary?.snapshotAfter ?? primary?.snapshotAfter ?? null,\n changes: secondary?.changes ?? primary?.changes ?? null,\n context: secondary?.context ?? primary?.context ?? null,\n }\n }\n\n private async persistLog<TInput>(\n commandId: string,\n options: CommandExecutionOptions<TInput>,\n metadata: CommandLogMetadata | null\n ): Promise<ActionLog | null> {\n if (!metadata) return null\n if (metadata.skipLog) return null\n const resourceKind =\n typeof metadata.resourceKind === 'string' ? metadata.resourceKind : null\n if (resourceKind && SKIPPED_ACTION_LOG_RESOURCE_KINDS.has(resourceKind)) {\n return null\n }\n let service: ActionLogService | null = null\n try {\n service = (options.ctx.container.resolve('actionLogService') as ActionLogService)\n } catch {\n service = null\n }\n if (!service) return null\n\n const tenantId = metadata.tenantId ?? options.ctx.auth?.tenantId ?? null\n const organizationId =\n metadata.organizationId ?? options.ctx.selectedOrganizationId ?? options.ctx.auth?.orgId ?? null\n const actorUserId = metadata.actorUserId ?? options.ctx.auth?.sub ?? null\n const payload: Record<string, unknown> = {\n tenantId: tenantId ?? undefined,\n organizationId: organizationId ?? undefined,\n actorUserId: actorUserId ?? undefined,\n commandId,\n }\n\n if (metadata) {\n if ('actionLabel' in metadata && metadata.actionLabel != null) payload.actionLabel = metadata.actionLabel\n if ('resourceKind' in metadata && metadata.resourceKind != null) payload.resourceKind = metadata.resourceKind\n if ('resourceId' in metadata && metadata.resourceId != null) payload.resourceId = metadata.resourceId\n if ('parentResourceKind' in metadata && metadata.parentResourceKind != null) payload.parentResourceKind = metadata.parentResourceKind\n if ('parentResourceId' in metadata && metadata.parentResourceId != null) payload.parentResourceId = metadata.parentResourceId\n if ('relatedResourceKind' in metadata && metadata.relatedResourceKind != null) payload.relatedResourceKind = metadata.relatedResourceKind\n if ('relatedResourceId' in metadata && metadata.relatedResourceId != null) payload.relatedResourceId = metadata.relatedResourceId\n if ('undoToken' in metadata && metadata.undoToken != null) payload.undoToken = metadata.undoToken\n if ('payload' in metadata && metadata.payload !== undefined) payload.commandPayload = metadata.payload\n if ('snapshotBefore' in metadata && metadata.snapshotBefore !== undefined) payload.snapshotBefore = metadata.snapshotBefore\n if ('snapshotAfter' in metadata && metadata.snapshotAfter !== undefined) payload.snapshotAfter = metadata.snapshotAfter\n if ('changes' in metadata && metadata.changes !== undefined && metadata.changes !== null) payload.changes = metadata.changes\n if ('context' in metadata && metadata.context !== undefined && metadata.context !== null) payload.context = metadata.context\n }\n\n const redoEnvelope = wrapRedoPayload('commandPayload' in payload ? (payload.commandPayload as unknown) : undefined, options.input)\n payload.commandPayload = redoEnvelope\n\n return await service.log(payload as ActionLogCreateInput)\n }\n\n private isUndoable(handler: CommandHandler<unknown, unknown>): boolean {\n return handler.isUndoable !== false && typeof handler.undo === 'function'\n }\n\n private async invalidateCacheAfterExecute<TResult>(\n commandId: string,\n options: CommandExecutionOptions<unknown>,\n result: TResult,\n metadata: CommandLogMetadata | null\n ): Promise<void> {\n const resource = typeof metadata?.resourceKind === 'string' ? metadata.resourceKind : null\n if (!resource) return\n try {\n const ctx = options.ctx\n const resultRecord = asRecord(result)\n const resultEntity = asRecord(resultRecord?.entity)\n const inputRecord = asRecord(options.input)\n const inputEntity = asRecord(inputRecord?.entity)\n\n const recordId = pickFirstIdentifier(\n metadata?.resourceId,\n extractPrimaryIdFromCommandResult(result),\n resultRecord?.entityId,\n resultRecord?.id,\n resultRecord?.recordId,\n resultEntity?.id,\n inputRecord?.id,\n inputRecord?.entityId,\n inputRecord?.recordId,\n inputEntity?.id\n )\n\n const organizationId = pickFirstIdentifier(\n metadata?.organizationId,\n resultRecord?.organizationId,\n resultEntity?.organizationId,\n inputRecord?.organizationId,\n inputEntity?.organizationId,\n ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null\n )\n\n const tenantId = pickFirstIdentifier(\n metadata?.tenantId,\n resultRecord?.tenantId,\n resultEntity?.tenantId,\n inputRecord?.tenantId,\n inputEntity?.tenantId,\n ctx.auth?.tenantId ?? null\n )\n\n const fallbackTenant = pickFirstIdentifier(metadata?.tenantId, ctx.auth?.tenantId ?? null)\n\n const aliasSet = new Set<string>()\n for (const alias of extractAliasList(metadata?.context ?? null)) {\n aliasSet.add(alias)\n }\n const derived = deriveResourceFromCommandId(commandId)\n if (derived) aliasSet.add(derived)\n const aliasExtras = Array.from(aliasSet)\n await invalidateCrudCache(\n ctx.container,\n resource,\n { id: recordId, organizationId, tenantId },\n fallbackTenant,\n `command:${commandId}:execute`,\n aliasExtras\n )\n } catch (err) {\n if (isCrudCacheDebugEnabled()) {\n try {\n logger.debug('Cache execute-invalidation failed', { commandId, err })\n } catch {}\n }\n }\n }\n\n private async invalidateCacheAfterUndo(log: ActionLog, ctx: CommandRuntimeContext): Promise<void> {\n const resource = typeof log.resourceKind === 'string' ? log.resourceKind : null\n if (!resource) return\n try {\n const recordId = pickFirstIdentifier(log.resourceId)\n const organizationId = pickFirstIdentifier(log.organizationId, ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null)\n const tenantId = pickFirstIdentifier(log.tenantId, ctx.auth?.tenantId ?? null)\n const fallbackTenant = pickFirstIdentifier(log.tenantId, ctx.auth?.tenantId ?? null)\n const aliasSet = new Set<string>()\n for (const alias of extractAliasList(log.contextJson ?? null)) {\n aliasSet.add(alias)\n }\n const derived = deriveResourceFromCommandId(log.commandId)\n if (derived) aliasSet.add(derived)\n const aliasExtras = Array.from(aliasSet)\n await invalidateCrudCache(\n ctx.container,\n resource,\n { id: recordId, organizationId, tenantId },\n fallbackTenant,\n `command:${log.commandId}:undo`,\n aliasExtras\n )\n } catch (err) {\n if (isCrudCacheDebugEnabled()) {\n try {\n logger.debug('Cache undo-invalidation failed', { commandId: log.commandId, err })\n } catch {}\n }\n }\n }\n\n private async flushCrudSideEffects(container: AwilixContainer, suppress?: BulkImportSuppression): Promise<void> {\n try {\n const dataEngine = (container.resolve('dataEngine') as DataEngine)\n await dataEngine.flushOrmEntityChanges(suppress)\n } catch (error) {\n if (isReadProjectionAlwaysConsistent()) {\n throw error\n }\n // best-effort: failures should not block command execution\n }\n }\n}\n\ntype RedoEnvelope = {\n __redoInput: unknown\n [key: string]: unknown\n}\n\nfunction wrapRedoPayload(existing: unknown, input: unknown): RedoEnvelope {\n if (!existing || typeof existing !== 'object' || Array.isArray(existing)) {\n const envelope: RedoEnvelope = { __redoInput: input }\n if (existing !== undefined) envelope.value = existing\n return envelope\n }\n const current = existing as Record<string, unknown>\n if ('__redoInput' in current && current.__redoInput !== undefined) {\n return current as RedoEnvelope\n }\n return { __redoInput: input, ...current }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { authorizeFeatures } from "../../security/featurePolicy.js";
|
|
2
2
|
import { createLogger } from "../logger/index.js";
|
|
3
3
|
const logger = createLogger("shared").child({ component: "commands" });
|
|
4
4
|
function matchesCommandPattern(pattern, commandId) {
|
|
@@ -11,7 +11,7 @@ function matchesCommandPattern(pattern, commandId) {
|
|
|
11
11
|
return false;
|
|
12
12
|
}
|
|
13
13
|
function collectMatching(interceptors, commandId, userFeatures) {
|
|
14
|
-
return interceptors.filter((i) => matchesCommandPattern(i.targetCommand, commandId)).filter((i) =>
|
|
14
|
+
return interceptors.filter((i) => matchesCommandPattern(i.targetCommand, commandId)).filter((i) => authorizeFeatures(i.features ?? [], { grantedFeatures: userFeatures })).sort((a, b) => (a.priority ?? 50) - (b.priority ?? 50));
|
|
15
15
|
}
|
|
16
16
|
async function runCommandInterceptorsBefore(interceptors, commandId, input, context, userFeatures) {
|
|
17
17
|
const matching = collectMatching(interceptors, commandId, userFeatures);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/commands/command-interceptor-runner.ts"],
|
|
4
|
-
"sourcesContent": ["import type {\n CommandInterceptor,\n CommandInterceptorContext,\n CommandInterceptorUndoContext,\n} from './command-interceptor'\nimport {
|
|
5
|
-
"mappings": "AAKA,SAAS,
|
|
4
|
+
"sourcesContent": ["import type {\n CommandInterceptor,\n CommandInterceptorContext,\n CommandInterceptorUndoContext,\n} from './command-interceptor'\nimport { authorizeFeatures } from '../../security/featurePolicy'\nimport { createLogger } from '../logger'\n\nconst logger = createLogger('shared').child({ component: 'commands' })\n\n// ---------------------------------------------------------------------------\n// Command pattern matching\n// ---------------------------------------------------------------------------\n\nexport function matchesCommandPattern(pattern: string, commandId: string): boolean {\n if (pattern === '*') return true\n if (pattern === commandId) return true\n if (pattern.endsWith('.*')) {\n const prefix = pattern.slice(0, -2)\n return commandId.startsWith(prefix + '.')\n }\n return false\n}\n\n// ---------------------------------------------------------------------------\n// Collect matching interceptors\n// ---------------------------------------------------------------------------\n\nfunction collectMatching(\n interceptors: CommandInterceptor[],\n commandId: string,\n userFeatures: string[],\n): CommandInterceptor[] {\n return interceptors\n .filter((i) => matchesCommandPattern(i.targetCommand, commandId))\n .filter((i) => authorizeFeatures(i.features ?? [], { grantedFeatures: userFeatures }))\n .sort((a, b) => (a.priority ?? 50) - (b.priority ?? 50))\n}\n\n// ---------------------------------------------------------------------------\n// Run beforeExecute interceptors\n// ---------------------------------------------------------------------------\n\nexport async function runCommandInterceptorsBefore(\n interceptors: CommandInterceptor[],\n commandId: string,\n input: unknown,\n context: CommandInterceptorContext,\n userFeatures: string[],\n): Promise<{\n ok: boolean\n error?: { message: string }\n modifiedInput?: Record<string, unknown>\n metadataByInterceptor: Map<string, Record<string, unknown>>\n}> {\n const matching = collectMatching(interceptors, commandId, userFeatures)\n\n let currentInput = input\n const metadataByInterceptor = new Map<string, Record<string, unknown>>()\n\n for (const interceptor of matching) {\n if (!interceptor.beforeExecute) continue\n const result = await interceptor.beforeExecute(currentInput, { ...context, commandId })\n\n if (result?.ok === false) {\n return {\n ok: false,\n error: { message: result.message ?? `Blocked by command interceptor: ${interceptor.id}` },\n metadataByInterceptor,\n }\n }\n\n if (result?.modifiedInput) {\n currentInput =\n typeof currentInput === 'object' && currentInput\n ? { ...(currentInput as Record<string, unknown>), ...result.modifiedInput }\n : result.modifiedInput\n }\n\n if (result?.metadata) {\n metadataByInterceptor.set(interceptor.id, result.metadata)\n }\n }\n\n const inputChanged = currentInput !== input\n return {\n ok: true,\n modifiedInput: inputChanged ? (currentInput as Record<string, unknown>) : undefined,\n metadataByInterceptor,\n }\n}\n\n// ---------------------------------------------------------------------------\n// Run afterExecute interceptors\n// ---------------------------------------------------------------------------\n\nexport async function runCommandInterceptorsAfter(\n interceptors: CommandInterceptor[],\n commandId: string,\n input: unknown,\n result: unknown,\n context: CommandInterceptorContext,\n userFeatures: string[],\n metadataByInterceptor: Map<string, Record<string, unknown>>,\n): Promise<{ modifiedResult?: Record<string, unknown> }> {\n const matching = collectMatching(interceptors, commandId, userFeatures)\n\n let currentResult = result\n\n for (const interceptor of matching) {\n if (!interceptor.afterExecute) continue\n try {\n const afterResult = await interceptor.afterExecute(\n input,\n currentResult,\n { ...context, commandId, metadata: metadataByInterceptor.get(interceptor.id) },\n )\n if (afterResult?.modifiedResult && typeof currentResult === 'object' && currentResult) {\n currentResult = { ...(currentResult as Record<string, unknown>), ...afterResult.modifiedResult }\n }\n } catch (error) {\n logger.error('Command interceptor afterExecute failed', { interceptorId: interceptor.id, err: error })\n }\n }\n\n const resultChanged = currentResult !== result\n return { modifiedResult: resultChanged ? (currentResult as Record<string, unknown>) : undefined }\n}\n\n// ---------------------------------------------------------------------------\n// Run beforeUndo interceptors\n// ---------------------------------------------------------------------------\n\nexport async function runCommandInterceptorsBeforeUndo(\n interceptors: CommandInterceptor[],\n commandId: string,\n undoContext: CommandInterceptorUndoContext,\n context: CommandInterceptorContext,\n userFeatures: string[],\n): Promise<{\n ok: boolean\n error?: { message: string }\n metadataByInterceptor: Map<string, Record<string, unknown>>\n}> {\n const matching = collectMatching(interceptors, commandId, userFeatures)\n\n const metadataByInterceptor = new Map<string, Record<string, unknown>>()\n\n for (const interceptor of matching) {\n if (!interceptor.beforeUndo) continue\n const result = await interceptor.beforeUndo(undoContext, { ...context, commandId })\n\n if (result?.ok === false) {\n return {\n ok: false,\n error: { message: result.message ?? `Undo blocked by command interceptor: ${interceptor.id}` },\n metadataByInterceptor,\n }\n }\n\n if (result?.metadata) {\n metadataByInterceptor.set(interceptor.id, result.metadata)\n }\n }\n\n return { ok: true, metadataByInterceptor }\n}\n\n// ---------------------------------------------------------------------------\n// Run afterUndo interceptors\n// ---------------------------------------------------------------------------\n\nexport async function runCommandInterceptorsAfterUndo(\n interceptors: CommandInterceptor[],\n commandId: string,\n undoContext: CommandInterceptorUndoContext,\n context: CommandInterceptorContext,\n userFeatures: string[],\n metadataByInterceptor: Map<string, Record<string, unknown>>,\n): Promise<void> {\n const matching = collectMatching(interceptors, commandId, userFeatures)\n\n for (const interceptor of matching) {\n if (!interceptor.afterUndo) continue\n try {\n await interceptor.afterUndo(\n undoContext,\n { ...context, commandId, metadata: metadataByInterceptor.get(interceptor.id) },\n )\n } catch (error) {\n logger.error('Command interceptor afterUndo failed', { interceptorId: interceptor.id, err: error })\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "AAKA,SAAS,yBAAyB;AAClC,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,QAAQ,EAAE,MAAM,EAAE,WAAW,WAAW,CAAC;AAM9D,SAAS,sBAAsB,SAAiB,WAA4B;AACjF,MAAI,YAAY,IAAK,QAAO;AAC5B,MAAI,YAAY,UAAW,QAAO;AAClC,MAAI,QAAQ,SAAS,IAAI,GAAG;AAC1B,UAAM,SAAS,QAAQ,MAAM,GAAG,EAAE;AAClC,WAAO,UAAU,WAAW,SAAS,GAAG;AAAA,EAC1C;AACA,SAAO;AACT;AAMA,SAAS,gBACP,cACA,WACA,cACsB;AACtB,SAAO,aACJ,OAAO,CAAC,MAAM,sBAAsB,EAAE,eAAe,SAAS,CAAC,EAC/D,OAAO,CAAC,MAAM,kBAAkB,EAAE,YAAY,CAAC,GAAG,EAAE,iBAAiB,aAAa,CAAC,CAAC,EACpF,KAAK,CAAC,GAAG,OAAO,EAAE,YAAY,OAAO,EAAE,YAAY,GAAG;AAC3D;AAMA,eAAsB,6BACpB,cACA,WACA,OACA,SACA,cAMC;AACD,QAAM,WAAW,gBAAgB,cAAc,WAAW,YAAY;AAEtE,MAAI,eAAe;AACnB,QAAM,wBAAwB,oBAAI,IAAqC;AAEvE,aAAW,eAAe,UAAU;AAClC,QAAI,CAAC,YAAY,cAAe;AAChC,UAAM,SAAS,MAAM,YAAY,cAAc,cAAc,EAAE,GAAG,SAAS,UAAU,CAAC;AAEtF,QAAI,QAAQ,OAAO,OAAO;AACxB,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,OAAO,EAAE,SAAS,OAAO,WAAW,mCAAmC,YAAY,EAAE,GAAG;AAAA,QACxF;AAAA,MACF;AAAA,IACF;AAEA,QAAI,QAAQ,eAAe;AACzB,qBACE,OAAO,iBAAiB,YAAY,eAChC,EAAE,GAAI,cAA0C,GAAG,OAAO,cAAc,IACxE,OAAO;AAAA,IACf;AAEA,QAAI,QAAQ,UAAU;AACpB,4BAAsB,IAAI,YAAY,IAAI,OAAO,QAAQ;AAAA,IAC3D;AAAA,EACF;AAEA,QAAM,eAAe,iBAAiB;AACtC,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,eAAe,eAAgB,eAA2C;AAAA,IAC1E;AAAA,EACF;AACF;AAMA,eAAsB,4BACpB,cACA,WACA,OACA,QACA,SACA,cACA,uBACuD;AACvD,QAAM,WAAW,gBAAgB,cAAc,WAAW,YAAY;AAEtE,MAAI,gBAAgB;AAEpB,aAAW,eAAe,UAAU;AAClC,QAAI,CAAC,YAAY,aAAc;AAC/B,QAAI;AACF,YAAM,cAAc,MAAM,YAAY;AAAA,QACpC;AAAA,QACA;AAAA,QACA,EAAE,GAAG,SAAS,WAAW,UAAU,sBAAsB,IAAI,YAAY,EAAE,EAAE;AAAA,MAC/E;AACA,UAAI,aAAa,kBAAkB,OAAO,kBAAkB,YAAY,eAAe;AACrF,wBAAgB,EAAE,GAAI,eAA2C,GAAG,YAAY,eAAe;AAAA,MACjG;AAAA,IACF,SAAS,OAAO;AACd,aAAO,MAAM,2CAA2C,EAAE,eAAe,YAAY,IAAI,KAAK,MAAM,CAAC;AAAA,IACvG;AAAA,EACF;AAEA,QAAM,gBAAgB,kBAAkB;AACxC,SAAO,EAAE,gBAAgB,gBAAiB,gBAA4C,OAAU;AAClG;AAMA,eAAsB,iCACpB,cACA,WACA,aACA,SACA,cAKC;AACD,QAAM,WAAW,gBAAgB,cAAc,WAAW,YAAY;AAEtE,QAAM,wBAAwB,oBAAI,IAAqC;AAEvE,aAAW,eAAe,UAAU;AAClC,QAAI,CAAC,YAAY,WAAY;AAC7B,UAAM,SAAS,MAAM,YAAY,WAAW,aAAa,EAAE,GAAG,SAAS,UAAU,CAAC;AAElF,QAAI,QAAQ,OAAO,OAAO;AACxB,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,OAAO,EAAE,SAAS,OAAO,WAAW,wCAAwC,YAAY,EAAE,GAAG;AAAA,QAC7F;AAAA,MACF;AAAA,IACF;AAEA,QAAI,QAAQ,UAAU;AACpB,4BAAsB,IAAI,YAAY,IAAI,OAAO,QAAQ;AAAA,IAC3D;AAAA,EACF;AAEA,SAAO,EAAE,IAAI,MAAM,sBAAsB;AAC3C;AAMA,eAAsB,gCACpB,cACA,WACA,aACA,SACA,cACA,uBACe;AACf,QAAM,WAAW,gBAAgB,cAAc,WAAW,YAAY;AAEtE,aAAW,eAAe,UAAU;AAClC,QAAI,CAAC,YAAY,UAAW;AAC5B,QAAI;AACF,YAAM,YAAY;AAAA,QAChB;AAAA,QACA,EAAE,GAAG,SAAS,WAAW,UAAU,sBAAsB,IAAI,YAAY,EAAE,EAAE;AAAA,MAC/E;AAAA,IACF,SAAS,OAAO;AACd,aAAO,MAAM,wCAAwC,EAAE,eAAe,YAAY,IAAI,KAAK,MAAM,CAAC;AAAA,IACpG;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getEnrichersForEntity } from "./enricher-registry.js";
|
|
2
2
|
import { logEnricherTiming } from "../umes/enricher-timing.js";
|
|
3
3
|
import { createLogger } from "../logger/index.js";
|
|
4
|
+
import { authorizeFeatures } from "../../security/featurePolicy.js";
|
|
4
5
|
const logger = createLogger("shared").child({ component: "umes" });
|
|
5
6
|
const DEFAULT_TIMEOUT = 2e3;
|
|
6
7
|
const SLOW_WARN_MS = 100;
|
|
@@ -14,17 +15,7 @@ function timeoutPromise(ms) {
|
|
|
14
15
|
function hasRequiredFeatures(enricher, userFeatures) {
|
|
15
16
|
if (!enricher.features || enricher.features.length === 0) return true;
|
|
16
17
|
if (!userFeatures) return false;
|
|
17
|
-
|
|
18
|
-
for (const granted of userFeatures) {
|
|
19
|
-
if (granted === "*" || granted === required) return true;
|
|
20
|
-
if (granted.endsWith(".*")) {
|
|
21
|
-
const prefix = granted.slice(0, -1);
|
|
22
|
-
if (required.startsWith(prefix)) return true;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return false;
|
|
26
|
-
};
|
|
27
|
-
return enricher.features.every((feature) => hasFeature(feature));
|
|
18
|
+
return authorizeFeatures(enricher.features, { grantedFeatures: userFeatures });
|
|
28
19
|
}
|
|
29
20
|
function filterByACLAndTenant(entries, context) {
|
|
30
21
|
return entries.filter((entry) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/crud/enricher-runner.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Response Enricher Runner\n *\n * Executes response enrichers against API response payloads.\n * Handles timeout, fallback, ACL feature gating, and error isolation.\n */\n\nimport type {\n EnricherContext,\n EnricherRegistryEntry,\n EnrichmentResult,\n ResponseEnricher,\n SingleEnrichmentResult,\n} from './response-enricher'\nimport { getEnrichersForEntity } from './enricher-registry'\nimport { logEnricherTiming } from '../umes/enricher-timing'\nimport { createLogger } from '../logger'\n\nconst logger = createLogger('shared').child({ component: 'umes' })\n\nconst DEFAULT_TIMEOUT = 2000\nconst SLOW_WARN_MS = 100\nconst SLOW_ERROR_MS = 500\nconst DEFAULT_CACHE_TTL_MS = 60_000\n\nfunction timeoutPromise(ms: number): Promise<never> {\n return new Promise((_, reject) =>\n setTimeout(() => reject(new Error(`Enricher timed out after ${ms}ms`)), ms),\n )\n}\n\nfunction hasRequiredFeatures(\n enricher: ResponseEnricher,\n userFeatures: string[] | undefined,\n): boolean {\n if (!enricher.features || enricher.features.length === 0) return true\n if (!userFeatures) return false\n const hasFeature = (required: string): boolean => {\n for (const granted of userFeatures) {\n if (granted === '*' || granted === required) return true\n if (granted.endsWith('.*')) {\n const prefix = granted.slice(0, -1)\n if (required.startsWith(prefix)) return true\n }\n }\n return false\n }\n return enricher.features.every((feature) => hasFeature(feature))\n}\n\nfunction filterByACLAndTenant(\n entries: EnricherRegistryEntry[],\n context: EnricherContext,\n): EnricherRegistryEntry[] {\n return entries.filter((entry) => {\n const enricher = entry.enricher\n if (!hasRequiredFeatures(enricher, context.userFeatures)) return false\n if (enricher.disabledTenantIds?.includes(context.tenantId)) return false\n return true\n })\n}\n\nfunction getActiveEnrichers(\n targetEntity: string,\n context: EnricherContext,\n): EnricherRegistryEntry[] {\n const entries = getEnrichersForEntity(targetEntity)\n return filterByACLAndTenant(entries, context)\n}\n\n/**\n * Plan describing whether (and how) a CRUD list cache may embed enricher output.\n */\nexport type ListCacheEnricherPlan = {\n /**\n * Stable signature of the active, cache-embeddable enrichers in registry\n * (priority) order. Included in the CRUD list cache key so a cached enriched\n * payload is only ever served back to a request whose entitlements select the\n * exact same enricher set. Empty string when nothing is embeddable \u2014 keeping\n * the cache key identical to the pre-enricher shape for unaffected routes.\n */\n signature: string\n /**\n * True only when there is at least one active enricher for the context AND\n * every active enricher opted into `cacheableOnListHit`. When true, the\n * enriched list payload may be stored in the cache and served on a hit without\n * re-running enrichers. When false, enrichers MUST re-run on every request so\n * the response reflects live data (cross-module reads, wall-clock values, etc.)\n * and no live enrichment is embedded in the shared cache entry.\n */\n skipEnrichersOnCacheHit: boolean\n}\n\n/**\n * Resolve, for the given context, whether the CRUD list cache may embed enricher\n * output and the cache-key signature to partition by when it can.\n *\n * The enriched payload is only embeddable (and the cache hit allowed to skip\n * enrichment) when every active enricher is `cacheableOnListHit` \u2014 i.e. its\n * output is a pure function of the cached record and invalidated together with\n * it. If any active enricher reads data the list cache does not invalidate on,\n * the route falls back to caching the pre-enrichment payload and re-running\n * enrichers on every request.\n */\nexport function resolveListCacheEnricherPlan(\n targetEntity: string,\n context: EnricherContext,\n): ListCacheEnricherPlan {\n const active = getActiveEnrichers(targetEntity, context)\n if (active.length === 0) return { signature: '', skipEnrichersOnCacheHit: false }\n const allCacheable = active.every((entry) => entry.enricher.cacheableOnListHit === true)\n if (!allCacheable) return { signature: '', skipEnrichersOnCacheHit: false }\n return {\n signature: active.map((entry) => entry.enricher.id).join(','),\n skipEnrichersOnCacheHit: true,\n }\n}\n\ntype CacheLike = {\n get: (key: string) => Promise<unknown>\n set: (key: string, value: unknown, options?: { ttl?: number; tags?: string[] }) => Promise<unknown>\n}\n\nfunction resolveCache(context: EnricherContext): CacheLike | null {\n const container = context.container as { resolve?: (name: string) => unknown } | undefined\n if (!container?.resolve) return null\n try {\n const cache = container.resolve('cache') as CacheLike\n if (cache && typeof cache.get === 'function' && typeof cache.set === 'function') {\n return cache\n }\n } catch {\n // ignore cache resolution failures\n }\n try {\n const cacheService = container.resolve('cacheService') as CacheLike\n if (cacheService && typeof cacheService.get === 'function' && typeof cacheService.set === 'function') {\n return cacheService\n }\n } catch {\n // ignore cache service resolution failures\n }\n return null\n}\n\nfunction buildCacheKey(\n enricher: ResponseEnricher,\n context: EnricherContext,\n mode: 'one' | 'many',\n recordIds: string[],\n): string {\n const sortedIds = [...recordIds].sort((a, b) => a.localeCompare(b))\n return `umes:enricher:${enricher.id}:tenant:${context.tenantId}:org:${context.organizationId}:mode:${mode}:ids:${JSON.stringify(sortedIds)}`\n}\n\nfunction extractRecordId(record: Record<string, unknown>): string {\n const idValue = record.id\n if (typeof idValue === 'string' && idValue.trim().length > 0) return idValue.trim()\n if (typeof idValue === 'number') return String(idValue)\n return 'unknown'\n}\n\nfunction getEnricherCacheTtl(enricher: ResponseEnricher): number {\n const ttl = enricher.cache?.ttl\n if (typeof ttl === 'number' && Number.isFinite(ttl) && ttl > 0) {\n return ttl\n }\n return DEFAULT_CACHE_TTL_MS\n}\n\nfunction getEnricherCacheTags(enricher: ResponseEnricher, context: EnricherContext): string[] {\n const tags = new Set<string>([\n `tenant:${context.tenantId}`,\n `organization:${context.organizationId}`,\n `enricher:${enricher.id}`,\n ])\n for (const tag of enricher.cache?.tags ?? []) {\n if (!tag || tag.trim().length === 0) continue\n tags.add(tag)\n }\n return Array.from(tags)\n}\n\nasync function readEnricherCache<T>(\n cache: CacheLike | null,\n key: string,\n): Promise<T | null> {\n if (!cache) return null\n try {\n const value = await cache.get(key)\n return value == null ? null : (value as T)\n } catch {\n return null\n }\n}\n\nasync function writeEnricherCache(\n cache: CacheLike | null,\n key: string,\n value: unknown,\n ttl: number,\n tags: string[],\n): Promise<void> {\n if (!cache) return\n try {\n await cache.set(key, value, { ttl, tags })\n } catch {\n // ignore cache write failures\n }\n}\n\n/**\n * Apply response enrichers to a list of records.\n *\n * Runs AFTER CrudHooks.afterList, BEFORE HTTP response serialization.\n * Each enricher runs independently \u2014 a failed non-critical enricher is skipped.\n */\nexport async function applyResponseEnrichers<T extends Record<string, unknown>>(\n items: T[],\n targetEntity: string,\n context: EnricherContext,\n preFilteredEntries?: EnricherRegistryEntry[],\n): Promise<EnrichmentResult<T>> {\n const activeEntries = preFilteredEntries\n ? filterByACLAndTenant(preFilteredEntries, context)\n : getActiveEnrichers(targetEntity, context)\n\n if (activeEntries.length === 0) {\n return { items, _meta: { enrichedBy: [] } }\n }\n\n const enrichedBy: string[] = []\n const enricherErrors: string[] = []\n let currentItems = items\n const cache = resolveCache(context)\n\n for (const entry of activeEntries) {\n const enricher = entry.enricher\n const timeout = enricher.timeout ?? DEFAULT_TIMEOUT\n const startTime = Date.now()\n\n try {\n let result: T[]\n const recordIds = currentItems.map((item) => extractRecordId(item))\n const shouldUseCache = enricher.cache?.strategy === 'read-through'\n const cacheKey = shouldUseCache ? buildCacheKey(enricher, context, 'many', recordIds) : null\n if (shouldUseCache && cacheKey) {\n const cached = await readEnricherCache<T[]>(cache, cacheKey)\n if (cached) {\n currentItems = cached\n enrichedBy.push(enricher.id)\n continue\n }\n }\n\n if (enricher.enrichMany) {\n result = await Promise.race([\n enricher.enrichMany(currentItems, context) as Promise<T[]>,\n timeoutPromise(timeout),\n ])\n } else {\n throw new Error(\n `Enricher ${enricher.id} must implement enrichMany() for list endpoints`,\n )\n }\n\n const elapsedMs = Date.now() - startTime\n if (elapsedMs > SLOW_ERROR_MS) {\n logger.error('Enricher exceeded slow threshold', { enricherId: enricher.id, elapsedMs, thresholdMs: SLOW_ERROR_MS })\n } else if (elapsedMs > SLOW_WARN_MS) {\n logger.warn('Enricher exceeded slow threshold', { enricherId: enricher.id, elapsedMs, thresholdMs: SLOW_WARN_MS })\n }\n logEnricherTiming(enricher.id, entry.moduleId, targetEntity, elapsedMs)\n\n currentItems = result\n if (shouldUseCache && cacheKey) {\n await writeEnricherCache(\n cache,\n cacheKey,\n result,\n getEnricherCacheTtl(enricher),\n getEnricherCacheTags(enricher, context),\n )\n }\n enrichedBy.push(enricher.id)\n } catch (err) {\n if (enricher.critical) {\n throw err\n }\n\n logger.warn('Enricher failed', { enricherId: enricher.id, err })\n enricherErrors.push(enricher.id)\n\n if (enricher.fallback) {\n currentItems = currentItems.map((item) => ({\n ...item,\n ...enricher.fallback,\n })) as T[]\n }\n }\n }\n\n return {\n items: currentItems,\n _meta: {\n enrichedBy,\n ...(enricherErrors.length > 0 ? { enricherErrors } : {}),\n },\n }\n}\n\n/**\n * Apply response enrichers to a single record.\n *\n * Used for detail endpoints (GET /:id), POST, and PUT responses.\n */\nexport async function applyResponseEnricherToRecord<T extends Record<string, unknown>>(\n record: T,\n targetEntity: string,\n context: EnricherContext,\n preFilteredEntries?: EnricherRegistryEntry[],\n): Promise<SingleEnrichmentResult<T>> {\n const activeEntries = preFilteredEntries\n ? filterByACLAndTenant(preFilteredEntries, context)\n : getActiveEnrichers(targetEntity, context)\n\n if (activeEntries.length === 0) {\n return { record, _meta: { enrichedBy: [] } }\n }\n\n const enrichedBy: string[] = []\n const enricherErrors: string[] = []\n let currentRecord = record\n const cache = resolveCache(context)\n\n for (const entry of activeEntries) {\n const enricher = entry.enricher\n const timeout = enricher.timeout ?? DEFAULT_TIMEOUT\n const startTime = Date.now()\n\n try {\n const recordId = extractRecordId(currentRecord)\n const shouldUseCache = enricher.cache?.strategy === 'read-through'\n const cacheKey = shouldUseCache ? buildCacheKey(enricher, context, 'one', [recordId]) : null\n if (shouldUseCache && cacheKey) {\n const cached = await readEnricherCache<T>(cache, cacheKey)\n if (cached) {\n currentRecord = cached\n enrichedBy.push(enricher.id)\n continue\n }\n }\n const result = await Promise.race([\n enricher.enrichOne(currentRecord, context) as Promise<T>,\n timeoutPromise(timeout),\n ])\n\n const elapsedMs = Date.now() - startTime\n logEnricherTiming(enricher.id, entry.moduleId, targetEntity, elapsedMs)\n\n currentRecord = result\n if (shouldUseCache && cacheKey) {\n await writeEnricherCache(\n cache,\n cacheKey,\n result,\n getEnricherCacheTtl(enricher),\n getEnricherCacheTags(enricher, context),\n )\n }\n enrichedBy.push(enricher.id)\n } catch (err) {\n if (enricher.critical) {\n throw err\n }\n\n logger.warn('Enricher failed', { enricherId: enricher.id, err })\n enricherErrors.push(enricher.id)\n\n if (enricher.fallback) {\n currentRecord = { ...currentRecord, ...enricher.fallback } as T\n }\n }\n }\n\n return {\n record: currentRecord,\n _meta: {\n enrichedBy,\n ...(enricherErrors.length > 0 ? { enricherErrors } : {}),\n },\n }\n}\n"],
|
|
5
|
-
"mappings": "AAcA,SAAS,6BAA6B;AACtC,SAAS,yBAAyB;AAClC,SAAS,oBAAoB;
|
|
4
|
+
"sourcesContent": ["/**\n * Response Enricher Runner\n *\n * Executes response enrichers against API response payloads.\n * Handles timeout, fallback, ACL feature gating, and error isolation.\n */\n\nimport type {\n EnricherContext,\n EnricherRegistryEntry,\n EnrichmentResult,\n ResponseEnricher,\n SingleEnrichmentResult,\n} from './response-enricher'\nimport { getEnrichersForEntity } from './enricher-registry'\nimport { logEnricherTiming } from '../umes/enricher-timing'\nimport { createLogger } from '../logger'\nimport { authorizeFeatures } from '../../security/featurePolicy'\n\nconst logger = createLogger('shared').child({ component: 'umes' })\n\nconst DEFAULT_TIMEOUT = 2000\nconst SLOW_WARN_MS = 100\nconst SLOW_ERROR_MS = 500\nconst DEFAULT_CACHE_TTL_MS = 60_000\n\nfunction timeoutPromise(ms: number): Promise<never> {\n return new Promise((_, reject) =>\n setTimeout(() => reject(new Error(`Enricher timed out after ${ms}ms`)), ms),\n )\n}\n\nfunction hasRequiredFeatures(\n enricher: ResponseEnricher,\n userFeatures: string[] | undefined,\n): boolean {\n if (!enricher.features || enricher.features.length === 0) return true\n if (!userFeatures) return false\n return authorizeFeatures(enricher.features, { grantedFeatures: userFeatures })\n}\n\nfunction filterByACLAndTenant(\n entries: EnricherRegistryEntry[],\n context: EnricherContext,\n): EnricherRegistryEntry[] {\n return entries.filter((entry) => {\n const enricher = entry.enricher\n if (!hasRequiredFeatures(enricher, context.userFeatures)) return false\n if (enricher.disabledTenantIds?.includes(context.tenantId)) return false\n return true\n })\n}\n\nfunction getActiveEnrichers(\n targetEntity: string,\n context: EnricherContext,\n): EnricherRegistryEntry[] {\n const entries = getEnrichersForEntity(targetEntity)\n return filterByACLAndTenant(entries, context)\n}\n\n/**\n * Plan describing whether (and how) a CRUD list cache may embed enricher output.\n */\nexport type ListCacheEnricherPlan = {\n /**\n * Stable signature of the active, cache-embeddable enrichers in registry\n * (priority) order. Included in the CRUD list cache key so a cached enriched\n * payload is only ever served back to a request whose entitlements select the\n * exact same enricher set. Empty string when nothing is embeddable \u2014 keeping\n * the cache key identical to the pre-enricher shape for unaffected routes.\n */\n signature: string\n /**\n * True only when there is at least one active enricher for the context AND\n * every active enricher opted into `cacheableOnListHit`. When true, the\n * enriched list payload may be stored in the cache and served on a hit without\n * re-running enrichers. When false, enrichers MUST re-run on every request so\n * the response reflects live data (cross-module reads, wall-clock values, etc.)\n * and no live enrichment is embedded in the shared cache entry.\n */\n skipEnrichersOnCacheHit: boolean\n}\n\n/**\n * Resolve, for the given context, whether the CRUD list cache may embed enricher\n * output and the cache-key signature to partition by when it can.\n *\n * The enriched payload is only embeddable (and the cache hit allowed to skip\n * enrichment) when every active enricher is `cacheableOnListHit` \u2014 i.e. its\n * output is a pure function of the cached record and invalidated together with\n * it. If any active enricher reads data the list cache does not invalidate on,\n * the route falls back to caching the pre-enrichment payload and re-running\n * enrichers on every request.\n */\nexport function resolveListCacheEnricherPlan(\n targetEntity: string,\n context: EnricherContext,\n): ListCacheEnricherPlan {\n const active = getActiveEnrichers(targetEntity, context)\n if (active.length === 0) return { signature: '', skipEnrichersOnCacheHit: false }\n const allCacheable = active.every((entry) => entry.enricher.cacheableOnListHit === true)\n if (!allCacheable) return { signature: '', skipEnrichersOnCacheHit: false }\n return {\n signature: active.map((entry) => entry.enricher.id).join(','),\n skipEnrichersOnCacheHit: true,\n }\n}\n\ntype CacheLike = {\n get: (key: string) => Promise<unknown>\n set: (key: string, value: unknown, options?: { ttl?: number; tags?: string[] }) => Promise<unknown>\n}\n\nfunction resolveCache(context: EnricherContext): CacheLike | null {\n const container = context.container as { resolve?: (name: string) => unknown } | undefined\n if (!container?.resolve) return null\n try {\n const cache = container.resolve('cache') as CacheLike\n if (cache && typeof cache.get === 'function' && typeof cache.set === 'function') {\n return cache\n }\n } catch {\n // ignore cache resolution failures\n }\n try {\n const cacheService = container.resolve('cacheService') as CacheLike\n if (cacheService && typeof cacheService.get === 'function' && typeof cacheService.set === 'function') {\n return cacheService\n }\n } catch {\n // ignore cache service resolution failures\n }\n return null\n}\n\nfunction buildCacheKey(\n enricher: ResponseEnricher,\n context: EnricherContext,\n mode: 'one' | 'many',\n recordIds: string[],\n): string {\n const sortedIds = [...recordIds].sort((a, b) => a.localeCompare(b))\n return `umes:enricher:${enricher.id}:tenant:${context.tenantId}:org:${context.organizationId}:mode:${mode}:ids:${JSON.stringify(sortedIds)}`\n}\n\nfunction extractRecordId(record: Record<string, unknown>): string {\n const idValue = record.id\n if (typeof idValue === 'string' && idValue.trim().length > 0) return idValue.trim()\n if (typeof idValue === 'number') return String(idValue)\n return 'unknown'\n}\n\nfunction getEnricherCacheTtl(enricher: ResponseEnricher): number {\n const ttl = enricher.cache?.ttl\n if (typeof ttl === 'number' && Number.isFinite(ttl) && ttl > 0) {\n return ttl\n }\n return DEFAULT_CACHE_TTL_MS\n}\n\nfunction getEnricherCacheTags(enricher: ResponseEnricher, context: EnricherContext): string[] {\n const tags = new Set<string>([\n `tenant:${context.tenantId}`,\n `organization:${context.organizationId}`,\n `enricher:${enricher.id}`,\n ])\n for (const tag of enricher.cache?.tags ?? []) {\n if (!tag || tag.trim().length === 0) continue\n tags.add(tag)\n }\n return Array.from(tags)\n}\n\nasync function readEnricherCache<T>(\n cache: CacheLike | null,\n key: string,\n): Promise<T | null> {\n if (!cache) return null\n try {\n const value = await cache.get(key)\n return value == null ? null : (value as T)\n } catch {\n return null\n }\n}\n\nasync function writeEnricherCache(\n cache: CacheLike | null,\n key: string,\n value: unknown,\n ttl: number,\n tags: string[],\n): Promise<void> {\n if (!cache) return\n try {\n await cache.set(key, value, { ttl, tags })\n } catch {\n // ignore cache write failures\n }\n}\n\n/**\n * Apply response enrichers to a list of records.\n *\n * Runs AFTER CrudHooks.afterList, BEFORE HTTP response serialization.\n * Each enricher runs independently \u2014 a failed non-critical enricher is skipped.\n */\nexport async function applyResponseEnrichers<T extends Record<string, unknown>>(\n items: T[],\n targetEntity: string,\n context: EnricherContext,\n preFilteredEntries?: EnricherRegistryEntry[],\n): Promise<EnrichmentResult<T>> {\n const activeEntries = preFilteredEntries\n ? filterByACLAndTenant(preFilteredEntries, context)\n : getActiveEnrichers(targetEntity, context)\n\n if (activeEntries.length === 0) {\n return { items, _meta: { enrichedBy: [] } }\n }\n\n const enrichedBy: string[] = []\n const enricherErrors: string[] = []\n let currentItems = items\n const cache = resolveCache(context)\n\n for (const entry of activeEntries) {\n const enricher = entry.enricher\n const timeout = enricher.timeout ?? DEFAULT_TIMEOUT\n const startTime = Date.now()\n\n try {\n let result: T[]\n const recordIds = currentItems.map((item) => extractRecordId(item))\n const shouldUseCache = enricher.cache?.strategy === 'read-through'\n const cacheKey = shouldUseCache ? buildCacheKey(enricher, context, 'many', recordIds) : null\n if (shouldUseCache && cacheKey) {\n const cached = await readEnricherCache<T[]>(cache, cacheKey)\n if (cached) {\n currentItems = cached\n enrichedBy.push(enricher.id)\n continue\n }\n }\n\n if (enricher.enrichMany) {\n result = await Promise.race([\n enricher.enrichMany(currentItems, context) as Promise<T[]>,\n timeoutPromise(timeout),\n ])\n } else {\n throw new Error(\n `Enricher ${enricher.id} must implement enrichMany() for list endpoints`,\n )\n }\n\n const elapsedMs = Date.now() - startTime\n if (elapsedMs > SLOW_ERROR_MS) {\n logger.error('Enricher exceeded slow threshold', { enricherId: enricher.id, elapsedMs, thresholdMs: SLOW_ERROR_MS })\n } else if (elapsedMs > SLOW_WARN_MS) {\n logger.warn('Enricher exceeded slow threshold', { enricherId: enricher.id, elapsedMs, thresholdMs: SLOW_WARN_MS })\n }\n logEnricherTiming(enricher.id, entry.moduleId, targetEntity, elapsedMs)\n\n currentItems = result\n if (shouldUseCache && cacheKey) {\n await writeEnricherCache(\n cache,\n cacheKey,\n result,\n getEnricherCacheTtl(enricher),\n getEnricherCacheTags(enricher, context),\n )\n }\n enrichedBy.push(enricher.id)\n } catch (err) {\n if (enricher.critical) {\n throw err\n }\n\n logger.warn('Enricher failed', { enricherId: enricher.id, err })\n enricherErrors.push(enricher.id)\n\n if (enricher.fallback) {\n currentItems = currentItems.map((item) => ({\n ...item,\n ...enricher.fallback,\n })) as T[]\n }\n }\n }\n\n return {\n items: currentItems,\n _meta: {\n enrichedBy,\n ...(enricherErrors.length > 0 ? { enricherErrors } : {}),\n },\n }\n}\n\n/**\n * Apply response enrichers to a single record.\n *\n * Used for detail endpoints (GET /:id), POST, and PUT responses.\n */\nexport async function applyResponseEnricherToRecord<T extends Record<string, unknown>>(\n record: T,\n targetEntity: string,\n context: EnricherContext,\n preFilteredEntries?: EnricherRegistryEntry[],\n): Promise<SingleEnrichmentResult<T>> {\n const activeEntries = preFilteredEntries\n ? filterByACLAndTenant(preFilteredEntries, context)\n : getActiveEnrichers(targetEntity, context)\n\n if (activeEntries.length === 0) {\n return { record, _meta: { enrichedBy: [] } }\n }\n\n const enrichedBy: string[] = []\n const enricherErrors: string[] = []\n let currentRecord = record\n const cache = resolveCache(context)\n\n for (const entry of activeEntries) {\n const enricher = entry.enricher\n const timeout = enricher.timeout ?? DEFAULT_TIMEOUT\n const startTime = Date.now()\n\n try {\n const recordId = extractRecordId(currentRecord)\n const shouldUseCache = enricher.cache?.strategy === 'read-through'\n const cacheKey = shouldUseCache ? buildCacheKey(enricher, context, 'one', [recordId]) : null\n if (shouldUseCache && cacheKey) {\n const cached = await readEnricherCache<T>(cache, cacheKey)\n if (cached) {\n currentRecord = cached\n enrichedBy.push(enricher.id)\n continue\n }\n }\n const result = await Promise.race([\n enricher.enrichOne(currentRecord, context) as Promise<T>,\n timeoutPromise(timeout),\n ])\n\n const elapsedMs = Date.now() - startTime\n logEnricherTiming(enricher.id, entry.moduleId, targetEntity, elapsedMs)\n\n currentRecord = result\n if (shouldUseCache && cacheKey) {\n await writeEnricherCache(\n cache,\n cacheKey,\n result,\n getEnricherCacheTtl(enricher),\n getEnricherCacheTags(enricher, context),\n )\n }\n enrichedBy.push(enricher.id)\n } catch (err) {\n if (enricher.critical) {\n throw err\n }\n\n logger.warn('Enricher failed', { enricherId: enricher.id, err })\n enricherErrors.push(enricher.id)\n\n if (enricher.fallback) {\n currentRecord = { ...currentRecord, ...enricher.fallback } as T\n }\n }\n }\n\n return {\n record: currentRecord,\n _meta: {\n enrichedBy,\n ...(enricherErrors.length > 0 ? { enricherErrors } : {}),\n },\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/lib/crud/factory.js
CHANGED
|
@@ -1760,7 +1760,8 @@ function makeCrudRoute(opts) {
|
|
|
1760
1760
|
recordId: String(created[ormCfg.idField]),
|
|
1761
1761
|
organizationId: targetOrgId,
|
|
1762
1762
|
tenantId: writeTenantId,
|
|
1763
|
-
values
|
|
1763
|
+
values,
|
|
1764
|
+
notify: false
|
|
1764
1765
|
});
|
|
1765
1766
|
}
|
|
1766
1767
|
}
|
|
@@ -2063,7 +2064,8 @@ function makeCrudRoute(opts) {
|
|
|
2063
2064
|
recordId: String(updated[ormCfg.idField]),
|
|
2064
2065
|
organizationId: targetOrgId,
|
|
2065
2066
|
tenantId: writeTenantId,
|
|
2066
|
-
values
|
|
2067
|
+
values,
|
|
2068
|
+
notify: false
|
|
2067
2069
|
});
|
|
2068
2070
|
}
|
|
2069
2071
|
}
|