@open-mercato/shared 0.4.2-canary-c02407ff85
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/build.mjs +101 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +7 -0
- package/dist/lib/api/crud.js +47 -0
- package/dist/lib/api/crud.js.map +7 -0
- package/dist/lib/api/scoped.js +140 -0
- package/dist/lib/api/scoped.js.map +7 -0
- package/dist/lib/auth/jwt.js +34 -0
- package/dist/lib/auth/jwt.js.map +7 -0
- package/dist/lib/auth/server.js +157 -0
- package/dist/lib/auth/server.js.map +7 -0
- package/dist/lib/boolean.js +22 -0
- package/dist/lib/boolean.js.map +7 -0
- package/dist/lib/bootstrap/appResolver.js +43 -0
- package/dist/lib/bootstrap/appResolver.js.map +7 -0
- package/dist/lib/bootstrap/dynamicLoader.js +108 -0
- package/dist/lib/bootstrap/dynamicLoader.js.map +7 -0
- package/dist/lib/bootstrap/factory.js +59 -0
- package/dist/lib/bootstrap/factory.js.map +7 -0
- package/dist/lib/bootstrap/index.js +11 -0
- package/dist/lib/bootstrap/index.js.map +7 -0
- package/dist/lib/bootstrap/types.js +1 -0
- package/dist/lib/bootstrap/types.js.map +7 -0
- package/dist/lib/cache/segments.js +36 -0
- package/dist/lib/cache/segments.js.map +7 -0
- package/dist/lib/cli/progress.js +46 -0
- package/dist/lib/cli/progress.js.map +7 -0
- package/dist/lib/commands/command-bus.js +285 -0
- package/dist/lib/commands/command-bus.js.map +7 -0
- package/dist/lib/commands/customFieldSnapshots.js +66 -0
- package/dist/lib/commands/customFieldSnapshots.js.map +7 -0
- package/dist/lib/commands/helpers.js +98 -0
- package/dist/lib/commands/helpers.js.map +7 -0
- package/dist/lib/commands/index.js +8 -0
- package/dist/lib/commands/index.js.map +7 -0
- package/dist/lib/commands/operationMetadata.js +32 -0
- package/dist/lib/commands/operationMetadata.js.map +7 -0
- package/dist/lib/commands/registry.js +43 -0
- package/dist/lib/commands/registry.js.map +7 -0
- package/dist/lib/commands/scope.js +44 -0
- package/dist/lib/commands/scope.js.map +7 -0
- package/dist/lib/commands/types.js +8 -0
- package/dist/lib/commands/types.js.map +7 -0
- package/dist/lib/crud/cache-stats.js +98 -0
- package/dist/lib/crud/cache-stats.js.map +7 -0
- package/dist/lib/crud/cache.js +175 -0
- package/dist/lib/crud/cache.js.map +7 -0
- package/dist/lib/crud/custom-fields-client.js +52 -0
- package/dist/lib/crud/custom-fields-client.js.map +7 -0
- package/dist/lib/crud/custom-fields.js +467 -0
- package/dist/lib/crud/custom-fields.js.map +7 -0
- package/dist/lib/crud/errors.js +24 -0
- package/dist/lib/crud/errors.js.map +7 -0
- package/dist/lib/crud/exporters.js +154 -0
- package/dist/lib/crud/exporters.js.map +7 -0
- package/dist/lib/crud/factory.js +1311 -0
- package/dist/lib/crud/factory.js.map +7 -0
- package/dist/lib/crud/types.js +1 -0
- package/dist/lib/crud/types.js.map +7 -0
- package/dist/lib/custom-fields/normalize.js +36 -0
- package/dist/lib/custom-fields/normalize.js.map +7 -0
- package/dist/lib/data/engine.js +396 -0
- package/dist/lib/data/engine.js.map +7 -0
- package/dist/lib/db/escapeLikePattern.js +5 -0
- package/dist/lib/db/escapeLikePattern.js.map +7 -0
- package/dist/lib/db/mikro.js +82 -0
- package/dist/lib/db/mikro.js.map +7 -0
- package/dist/lib/di/container.js +94 -0
- package/dist/lib/di/container.js.map +7 -0
- package/dist/lib/email/send.js +12 -0
- package/dist/lib/email/send.js.map +7 -0
- package/dist/lib/encryption/aes.js +58 -0
- package/dist/lib/encryption/aes.js.map +7 -0
- package/dist/lib/encryption/customFieldValues.js +49 -0
- package/dist/lib/encryption/customFieldValues.js.map +7 -0
- package/dist/lib/encryption/entityFields.js +26 -0
- package/dist/lib/encryption/entityFields.js.map +7 -0
- package/dist/lib/encryption/entityIds.js +80 -0
- package/dist/lib/encryption/entityIds.js.map +7 -0
- package/dist/lib/encryption/find.js +45 -0
- package/dist/lib/encryption/find.js.map +7 -0
- package/dist/lib/encryption/indexDoc.js +69 -0
- package/dist/lib/encryption/indexDoc.js.map +7 -0
- package/dist/lib/encryption/kms.js +282 -0
- package/dist/lib/encryption/kms.js.map +7 -0
- package/dist/lib/encryption/subscriber.js +330 -0
- package/dist/lib/encryption/subscriber.js.map +7 -0
- package/dist/lib/encryption/tenantDataEncryptionService.js +252 -0
- package/dist/lib/encryption/tenantDataEncryptionService.js.map +7 -0
- package/dist/lib/encryption/toggles.js +18 -0
- package/dist/lib/encryption/toggles.js.map +7 -0
- package/dist/lib/entities/naming.js +9 -0
- package/dist/lib/entities/naming.js.map +7 -0
- package/dist/lib/entities/system-entities.js +43 -0
- package/dist/lib/entities/system-entities.js.map +7 -0
- package/dist/lib/frontend/organizationEvents.js +41 -0
- package/dist/lib/frontend/organizationEvents.js.map +7 -0
- package/dist/lib/frontend/useOrganizationScope.js +32 -0
- package/dist/lib/frontend/useOrganizationScope.js.map +7 -0
- package/dist/lib/hotkeys/index.js +128 -0
- package/dist/lib/hotkeys/index.js.map +7 -0
- package/dist/lib/i18n/app-dictionaries.js +17 -0
- package/dist/lib/i18n/app-dictionaries.js.map +7 -0
- package/dist/lib/i18n/config.js +7 -0
- package/dist/lib/i18n/config.js.map +7 -0
- package/dist/lib/i18n/context.js +50 -0
- package/dist/lib/i18n/context.js.map +7 -0
- package/dist/lib/i18n/server.js +68 -0
- package/dist/lib/i18n/server.js.map +7 -0
- package/dist/lib/i18n/translate.js +45 -0
- package/dist/lib/i18n/translate.js.map +7 -0
- package/dist/lib/indexers/error-log.js +82 -0
- package/dist/lib/indexers/error-log.js.map +7 -0
- package/dist/lib/indexers/status-log.js +80 -0
- package/dist/lib/indexers/status-log.js.map +7 -0
- package/dist/lib/lib/auth/jwt.js +34 -0
- package/dist/lib/lib/auth/jwt.js.map +7 -0
- package/dist/lib/lib/auth/server.js +77 -0
- package/dist/lib/lib/auth/server.js.map +7 -0
- package/dist/lib/lib/email/send.js +12 -0
- package/dist/lib/lib/email/send.js.map +7 -0
- package/dist/lib/lib/i18n/config.js +7 -0
- package/dist/lib/lib/i18n/config.js.map +7 -0
- package/dist/lib/lib/i18n/context.js +31 -0
- package/dist/lib/lib/i18n/context.js.map +7 -0
- package/dist/lib/lib/utils.js +9 -0
- package/dist/lib/lib/utils.js.map +7 -0
- package/dist/lib/location/countries.js +68 -0
- package/dist/lib/location/countries.js.map +7 -0
- package/dist/lib/modules/index.js +6 -0
- package/dist/lib/modules/index.js.map +7 -0
- package/dist/lib/modules/registry.js +18 -0
- package/dist/lib/modules/registry.js.map +7 -0
- package/dist/lib/openapi/crud.js +137 -0
- package/dist/lib/openapi/crud.js.map +7 -0
- package/dist/lib/openapi/generator.js +1131 -0
- package/dist/lib/openapi/generator.js.map +7 -0
- package/dist/lib/openapi/index.js +10 -0
- package/dist/lib/openapi/index.js.map +7 -0
- package/dist/lib/openapi/sanitize.js +110 -0
- package/dist/lib/openapi/sanitize.js.map +7 -0
- package/dist/lib/openapi/types.js +1 -0
- package/dist/lib/openapi/types.js.map +7 -0
- package/dist/lib/profiler/index.js +258 -0
- package/dist/lib/profiler/index.js.map +7 -0
- package/dist/lib/query/engine.js +729 -0
- package/dist/lib/query/engine.js.map +7 -0
- package/dist/lib/query/join-utils.js +195 -0
- package/dist/lib/query/join-utils.js.map +7 -0
- package/dist/lib/query/types.js +9 -0
- package/dist/lib/query/types.js.map +7 -0
- package/dist/lib/search/config.js +32 -0
- package/dist/lib/search/config.js.map +7 -0
- package/dist/lib/search/tokenize.js +34 -0
- package/dist/lib/search/tokenize.js.map +7 -0
- package/dist/lib/slugify.js +24 -0
- package/dist/lib/slugify.js.map +7 -0
- package/dist/lib/testing/bootstrap.js +51 -0
- package/dist/lib/testing/bootstrap.js.map +7 -0
- package/dist/lib/testing/index.js +17 -0
- package/dist/lib/testing/index.js.map +7 -0
- package/dist/lib/testing/renderWithProviders.js +15 -0
- package/dist/lib/testing/renderWithProviders.js.map +7 -0
- package/dist/lib/url.js +12 -0
- package/dist/lib/url.js.map +7 -0
- package/dist/lib/utils.js +13 -0
- package/dist/lib/utils.js.map +7 -0
- package/dist/lib/version.js +7 -0
- package/dist/lib/version.js.map +7 -0
- package/dist/modules/dashboard/widgets.js +1 -0
- package/dist/modules/dashboard/widgets.js.map +7 -0
- package/dist/modules/dsl.js +30 -0
- package/dist/modules/dsl.js.map +7 -0
- package/dist/modules/entities/kinds.js +22 -0
- package/dist/modules/entities/kinds.js.map +7 -0
- package/dist/modules/entities/options.js +26 -0
- package/dist/modules/entities/options.js.map +7 -0
- package/dist/modules/entities/validation.js +102 -0
- package/dist/modules/entities/validation.js.map +7 -0
- package/dist/modules/entities/validators.js +88 -0
- package/dist/modules/entities/validators.js.map +7 -0
- package/dist/modules/entities.js +1 -0
- package/dist/modules/entities.js.map +7 -0
- package/dist/modules/navigation/sidebarPreferences.js +50 -0
- package/dist/modules/navigation/sidebarPreferences.js.map +7 -0
- package/dist/modules/perspectives/types.js +1 -0
- package/dist/modules/perspectives/types.js.map +7 -0
- package/dist/modules/registry.js +96 -0
- package/dist/modules/registry.js.map +7 -0
- package/dist/modules/search.js +15 -0
- package/dist/modules/search.js.map +7 -0
- package/dist/modules/vector.js +1 -0
- package/dist/modules/vector.js.map +7 -0
- package/dist/modules/widgets/injection-loader.js +180 -0
- package/dist/modules/widgets/injection-loader.js.map +7 -0
- package/dist/modules/widgets/injection.js +1 -0
- package/dist/modules/widgets/injection.js.map +7 -0
- package/dist/security/features.js +23 -0
- package/dist/security/features.js.map +7 -0
- package/dist/types/pg.d.js +1 -0
- package/dist/types/pg.d.js.map +7 -0
- package/dist/types/react-email.d.js +1 -0
- package/dist/types/react-email.d.js.map +7 -0
- package/dist/types/resend.d.js +1 -0
- package/dist/types/resend.d.js.map +7 -0
- package/jest.config.cjs +22 -0
- package/package.json +88 -0
- package/src/index.ts +0 -0
- package/src/lib/api/__tests__/scoped.test.ts +38 -0
- package/src/lib/api/crud.ts +59 -0
- package/src/lib/api/scoped.ts +239 -0
- package/src/lib/auth/jwt.ts +39 -0
- package/src/lib/auth/server.ts +199 -0
- package/src/lib/boolean.ts +17 -0
- package/src/lib/bootstrap/appResolver.ts +85 -0
- package/src/lib/bootstrap/dynamicLoader.ts +177 -0
- package/src/lib/bootstrap/factory.ts +108 -0
- package/src/lib/bootstrap/index.ts +23 -0
- package/src/lib/bootstrap/types.ts +31 -0
- package/src/lib/cache/segments.ts +56 -0
- package/src/lib/cli/progress.ts +55 -0
- package/src/lib/commands/__tests__/command-bus.test.ts +84 -0
- package/src/lib/commands/__tests__/helpers.test.ts +42 -0
- package/src/lib/commands/command-bus.ts +349 -0
- package/src/lib/commands/customFieldSnapshots.ts +86 -0
- package/src/lib/commands/helpers.ts +143 -0
- package/src/lib/commands/index.ts +4 -0
- package/src/lib/commands/operationMetadata.ts +40 -0
- package/src/lib/commands/registry.ts +46 -0
- package/src/lib/commands/scope.ts +59 -0
- package/src/lib/commands/types.ts +63 -0
- package/src/lib/crud/__tests__/crud-factory.test.ts +333 -0
- package/src/lib/crud/__tests__/custom-fields.test.ts +150 -0
- package/src/lib/crud/cache-stats.ts +127 -0
- package/src/lib/crud/cache.ts +205 -0
- package/src/lib/crud/custom-fields-client.ts +54 -0
- package/src/lib/crud/custom-fields.ts +607 -0
- package/src/lib/crud/errors.ts +23 -0
- package/src/lib/crud/exporters.ts +188 -0
- package/src/lib/crud/factory.ts +1622 -0
- package/src/lib/crud/types.ts +29 -0
- package/src/lib/custom-fields/normalize.ts +45 -0
- package/src/lib/data/engine.ts +562 -0
- package/src/lib/db/escapeLikePattern.ts +2 -0
- package/src/lib/db/mikro.ts +100 -0
- package/src/lib/di/container.ts +105 -0
- package/src/lib/email/send.ts +18 -0
- package/src/lib/encryption/__tests__/customFieldValues.test.ts +63 -0
- package/src/lib/encryption/__tests__/indexDoc.test.ts +115 -0
- package/src/lib/encryption/aes.ts +64 -0
- package/src/lib/encryption/customFieldValues.ts +67 -0
- package/src/lib/encryption/entityFields.ts +39 -0
- package/src/lib/encryption/entityIds.ts +107 -0
- package/src/lib/encryption/find.ts +81 -0
- package/src/lib/encryption/indexDoc.ts +104 -0
- package/src/lib/encryption/kms.ts +337 -0
- package/src/lib/encryption/subscriber.ts +416 -0
- package/src/lib/encryption/tenantDataEncryptionService.ts +313 -0
- package/src/lib/encryption/toggles.ts +15 -0
- package/src/lib/entities/naming.ts +6 -0
- package/src/lib/entities/system-entities.ts +43 -0
- package/src/lib/frontend/organizationEvents.ts +55 -0
- package/src/lib/frontend/useOrganizationScope.ts +30 -0
- package/src/lib/hotkeys/index.ts +168 -0
- package/src/lib/i18n/app-dictionaries.ts +18 -0
- package/src/lib/i18n/config.ts +4 -0
- package/src/lib/i18n/context.tsx +66 -0
- package/src/lib/i18n/server.ts +74 -0
- package/src/lib/i18n/translate.ts +54 -0
- package/src/lib/indexers/error-log.ts +106 -0
- package/src/lib/indexers/status-log.ts +119 -0
- package/src/lib/lib/auth/jwt.ts +39 -0
- package/src/lib/lib/auth/server.ts +94 -0
- package/src/lib/lib/email/send.ts +18 -0
- package/src/lib/lib/i18n/config.ts +4 -0
- package/src/lib/lib/i18n/context.tsx +38 -0
- package/src/lib/lib/utils.ts +6 -0
- package/src/lib/location/countries.ts +97 -0
- package/src/lib/modules/index.ts +1 -0
- package/src/lib/modules/registry.ts +18 -0
- package/src/lib/openapi/crud.ts +218 -0
- package/src/lib/openapi/generator.ts +1311 -0
- package/src/lib/openapi/index.ts +4 -0
- package/src/lib/openapi/sanitize.ts +137 -0
- package/src/lib/openapi/types.ts +79 -0
- package/src/lib/profiler/index.ts +371 -0
- package/src/lib/query/__tests__/engine.test.ts +274 -0
- package/src/lib/query/engine.ts +837 -0
- package/src/lib/query/join-utils.ts +238 -0
- package/src/lib/query/types.ts +121 -0
- package/src/lib/search/config.ts +49 -0
- package/src/lib/search/tokenize.ts +45 -0
- package/src/lib/slugify.ts +28 -0
- package/src/lib/testing/bootstrap.ts +124 -0
- package/src/lib/testing/index.ts +15 -0
- package/src/lib/testing/renderWithProviders.tsx +31 -0
- package/src/lib/url.ts +12 -0
- package/src/lib/utils.ts +17 -0
- package/src/lib/version.ts +5 -0
- package/src/modules/__tests__/dsl.test.ts +35 -0
- package/src/modules/__tests__/registry.test.ts +300 -0
- package/src/modules/dashboard/widgets.ts +57 -0
- package/src/modules/dsl.ts +32 -0
- package/src/modules/entities/__tests__/validation.test.ts +52 -0
- package/src/modules/entities/kinds.ts +20 -0
- package/src/modules/entities/options.ts +36 -0
- package/src/modules/entities/validation.ts +118 -0
- package/src/modules/entities/validators.ts +93 -0
- package/src/modules/entities.ts +102 -0
- package/src/modules/navigation/sidebarPreferences.ts +62 -0
- package/src/modules/perspectives/types.ts +40 -0
- package/src/modules/registry.ts +249 -0
- package/src/modules/search.ts +325 -0
- package/src/modules/vector.ts +122 -0
- package/src/modules/widgets/__tests__/injection.test.ts +48 -0
- package/src/modules/widgets/injection-loader.ts +235 -0
- package/src/modules/widgets/injection.ts +120 -0
- package/src/security/features.ts +22 -0
- package/src/types/pg.d.ts +2 -0
- package/src/types/react-email.d.ts +2 -0
- package/src/types/resend.d.ts +2 -0
- package/tsconfig.build.json +11 -0
- package/tsconfig.json +9 -0
- package/watch.mjs +6 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { commandRegistry } from "./registry.js";
|
|
2
|
+
import { defaultUndoToken } from "./types.js";
|
|
3
|
+
import {
|
|
4
|
+
canonicalizeResourceTag,
|
|
5
|
+
deriveResourceFromCommandId,
|
|
6
|
+
invalidateCrudCache,
|
|
7
|
+
pickFirstIdentifier,
|
|
8
|
+
isCrudCacheDebugEnabled
|
|
9
|
+
} from "@open-mercato/shared/lib/crud/cache";
|
|
10
|
+
const SKIPPED_ACTION_LOG_RESOURCE_KINDS = /* @__PURE__ */ new Set([
|
|
11
|
+
"audit_logs.access",
|
|
12
|
+
"audit_logs.action",
|
|
13
|
+
"dashboards.layout",
|
|
14
|
+
"dashboards.user_widgets",
|
|
15
|
+
"dashboards.role_widgets"
|
|
16
|
+
]);
|
|
17
|
+
function asRecord(input) {
|
|
18
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return null;
|
|
19
|
+
return input;
|
|
20
|
+
}
|
|
21
|
+
function extractAliasList(source) {
|
|
22
|
+
if (!source || typeof source !== "object" || Array.isArray(source)) return [];
|
|
23
|
+
const record = source;
|
|
24
|
+
const raw = record.cacheAliases;
|
|
25
|
+
if (!Array.isArray(raw)) return [];
|
|
26
|
+
const aliases = /* @__PURE__ */ new Set();
|
|
27
|
+
for (const value of raw) {
|
|
28
|
+
if (typeof value !== "string") continue;
|
|
29
|
+
const normalized = canonicalizeResourceTag(value);
|
|
30
|
+
if (normalized) aliases.add(normalized);
|
|
31
|
+
}
|
|
32
|
+
return Array.from(aliases);
|
|
33
|
+
}
|
|
34
|
+
class CommandBus {
|
|
35
|
+
async execute(commandId, options) {
|
|
36
|
+
const handler = this.resolveHandler(commandId);
|
|
37
|
+
const snapshots = await this.prepareSnapshots(handler, options);
|
|
38
|
+
const result = await handler.execute(options.input, options.ctx);
|
|
39
|
+
const afterSnapshot = await this.captureAfter(handler, options, result);
|
|
40
|
+
const snapshotsWithAfter = { ...snapshots, after: afterSnapshot };
|
|
41
|
+
const logMeta = await this.buildLog(handler, options, result, snapshotsWithAfter);
|
|
42
|
+
let mergedMeta = this.mergeMetadata(options.metadata, logMeta);
|
|
43
|
+
const undoable = this.isUndoable(handler);
|
|
44
|
+
if (undoable) {
|
|
45
|
+
mergedMeta = mergedMeta ?? {};
|
|
46
|
+
if (!mergedMeta.undoToken) mergedMeta.undoToken = defaultUndoToken();
|
|
47
|
+
if (mergedMeta.actorUserId === void 0) mergedMeta.actorUserId = options.ctx.auth?.sub ?? null;
|
|
48
|
+
}
|
|
49
|
+
if (afterSnapshot !== void 0 && afterSnapshot !== null) {
|
|
50
|
+
if (!mergedMeta) {
|
|
51
|
+
mergedMeta = { snapshotAfter: afterSnapshot };
|
|
52
|
+
} else if (!mergedMeta.snapshotAfter) {
|
|
53
|
+
mergedMeta.snapshotAfter = afterSnapshot;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (snapshots.before) {
|
|
57
|
+
if (!mergedMeta) {
|
|
58
|
+
mergedMeta = { snapshotBefore: snapshots.before };
|
|
59
|
+
} else if (!mergedMeta.snapshotBefore) {
|
|
60
|
+
mergedMeta.snapshotBefore = snapshots.before;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const logEntry = await this.persistLog(commandId, options, mergedMeta);
|
|
64
|
+
await this.invalidateCacheAfterExecute(commandId, options, result, mergedMeta);
|
|
65
|
+
await this.flushCrudSideEffects(options.ctx.container);
|
|
66
|
+
return { result, logEntry };
|
|
67
|
+
}
|
|
68
|
+
async undo(undoToken, ctx) {
|
|
69
|
+
const service = ctx.container.resolve("actionLogService");
|
|
70
|
+
const log = await service.findByUndoToken(undoToken);
|
|
71
|
+
if (!log) throw new Error("Undo token expired or not found");
|
|
72
|
+
const handler = this.resolveHandler(log.commandId);
|
|
73
|
+
if (!handler.undo || this.isUndoable(handler) === false) {
|
|
74
|
+
throw new Error(`Command ${log.commandId} is not undoable`);
|
|
75
|
+
}
|
|
76
|
+
await handler.undo({
|
|
77
|
+
input: log.commandPayload,
|
|
78
|
+
ctx,
|
|
79
|
+
logEntry: log
|
|
80
|
+
});
|
|
81
|
+
await service.markUndone(log.id);
|
|
82
|
+
await this.invalidateCacheAfterUndo(log, ctx);
|
|
83
|
+
await this.flushCrudSideEffects(ctx.container);
|
|
84
|
+
}
|
|
85
|
+
resolveHandler(commandId) {
|
|
86
|
+
const handler = commandRegistry.get(commandId);
|
|
87
|
+
if (!handler) throw new Error(`Command handler not registered for id ${commandId}`);
|
|
88
|
+
return handler;
|
|
89
|
+
}
|
|
90
|
+
async prepareSnapshots(handler, options) {
|
|
91
|
+
if (!handler.prepare) return {};
|
|
92
|
+
try {
|
|
93
|
+
return await handler.prepare(options.input, options.ctx) || {};
|
|
94
|
+
} catch (err) {
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async captureAfter(handler, options, result) {
|
|
99
|
+
if (!handler.captureAfter) return void 0;
|
|
100
|
+
return handler.captureAfter(options.input, result, options.ctx);
|
|
101
|
+
}
|
|
102
|
+
async buildLog(handler, options, result, snapshots) {
|
|
103
|
+
if (!handler.buildLog) return null;
|
|
104
|
+
const args = {
|
|
105
|
+
input: options.input,
|
|
106
|
+
result,
|
|
107
|
+
ctx: options.ctx,
|
|
108
|
+
snapshots
|
|
109
|
+
};
|
|
110
|
+
return await handler.buildLog(args) || null;
|
|
111
|
+
}
|
|
112
|
+
mergeMetadata(primary, secondary) {
|
|
113
|
+
if (!primary && !secondary) return null;
|
|
114
|
+
return {
|
|
115
|
+
tenantId: primary?.tenantId ?? secondary?.tenantId ?? null,
|
|
116
|
+
organizationId: primary?.organizationId ?? secondary?.organizationId ?? null,
|
|
117
|
+
actorUserId: primary?.actorUserId ?? secondary?.actorUserId ?? null,
|
|
118
|
+
actionLabel: primary?.actionLabel ?? secondary?.actionLabel ?? null,
|
|
119
|
+
resourceKind: primary?.resourceKind ?? secondary?.resourceKind ?? null,
|
|
120
|
+
resourceId: primary?.resourceId ?? secondary?.resourceId ?? null,
|
|
121
|
+
undoToken: primary?.undoToken ?? secondary?.undoToken ?? null,
|
|
122
|
+
payload: primary?.payload ?? secondary?.payload ?? null,
|
|
123
|
+
snapshotBefore: primary?.snapshotBefore ?? secondary?.snapshotBefore ?? null,
|
|
124
|
+
snapshotAfter: primary?.snapshotAfter ?? secondary?.snapshotAfter ?? null,
|
|
125
|
+
changes: primary?.changes ?? secondary?.changes ?? null,
|
|
126
|
+
context: primary?.context ?? secondary?.context ?? null
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
async persistLog(commandId, options, metadata) {
|
|
130
|
+
if (!metadata) return null;
|
|
131
|
+
const resourceKind = typeof metadata.resourceKind === "string" ? metadata.resourceKind : null;
|
|
132
|
+
if (resourceKind && SKIPPED_ACTION_LOG_RESOURCE_KINDS.has(resourceKind)) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
let service = null;
|
|
136
|
+
try {
|
|
137
|
+
service = options.ctx.container.resolve("actionLogService");
|
|
138
|
+
} catch {
|
|
139
|
+
service = null;
|
|
140
|
+
}
|
|
141
|
+
if (!service) return null;
|
|
142
|
+
const tenantId = metadata.tenantId ?? options.ctx.auth?.tenantId ?? null;
|
|
143
|
+
const organizationId = metadata.organizationId ?? options.ctx.selectedOrganizationId ?? options.ctx.auth?.orgId ?? null;
|
|
144
|
+
const actorUserId = metadata.actorUserId ?? options.ctx.auth?.sub ?? null;
|
|
145
|
+
const payload = {
|
|
146
|
+
tenantId: tenantId ?? void 0,
|
|
147
|
+
organizationId: organizationId ?? void 0,
|
|
148
|
+
actorUserId: actorUserId ?? void 0,
|
|
149
|
+
commandId
|
|
150
|
+
};
|
|
151
|
+
if (metadata) {
|
|
152
|
+
if ("actionLabel" in metadata && metadata.actionLabel != null) payload.actionLabel = metadata.actionLabel;
|
|
153
|
+
if ("resourceKind" in metadata && metadata.resourceKind != null) payload.resourceKind = metadata.resourceKind;
|
|
154
|
+
if ("resourceId" in metadata && metadata.resourceId != null) payload.resourceId = metadata.resourceId;
|
|
155
|
+
if ("undoToken" in metadata && metadata.undoToken != null) payload.undoToken = metadata.undoToken;
|
|
156
|
+
if ("payload" in metadata && metadata.payload !== void 0) payload.commandPayload = metadata.payload;
|
|
157
|
+
if ("snapshotBefore" in metadata && metadata.snapshotBefore !== void 0) payload.snapshotBefore = metadata.snapshotBefore;
|
|
158
|
+
if ("snapshotAfter" in metadata && metadata.snapshotAfter !== void 0) payload.snapshotAfter = metadata.snapshotAfter;
|
|
159
|
+
if ("changes" in metadata && metadata.changes !== void 0 && metadata.changes !== null) payload.changes = metadata.changes;
|
|
160
|
+
if ("context" in metadata && metadata.context !== void 0 && metadata.context !== null) payload.context = metadata.context;
|
|
161
|
+
}
|
|
162
|
+
const redoEnvelope = wrapRedoPayload("commandPayload" in payload ? payload.commandPayload : void 0, options.input);
|
|
163
|
+
payload.commandPayload = redoEnvelope;
|
|
164
|
+
return await service.log(payload);
|
|
165
|
+
}
|
|
166
|
+
isUndoable(handler) {
|
|
167
|
+
return handler.isUndoable !== false && typeof handler.undo === "function";
|
|
168
|
+
}
|
|
169
|
+
async invalidateCacheAfterExecute(commandId, options, result, metadata) {
|
|
170
|
+
const resource = typeof metadata?.resourceKind === "string" ? metadata.resourceKind : null;
|
|
171
|
+
if (!resource) return;
|
|
172
|
+
try {
|
|
173
|
+
const ctx = options.ctx;
|
|
174
|
+
const resultRecord = asRecord(result);
|
|
175
|
+
const resultEntity = asRecord(resultRecord?.entity);
|
|
176
|
+
const inputRecord = asRecord(options.input);
|
|
177
|
+
const inputEntity = asRecord(inputRecord?.entity);
|
|
178
|
+
const recordId = pickFirstIdentifier(
|
|
179
|
+
metadata?.resourceId,
|
|
180
|
+
resultRecord?.entityId,
|
|
181
|
+
resultRecord?.id,
|
|
182
|
+
resultRecord?.recordId,
|
|
183
|
+
resultEntity?.id,
|
|
184
|
+
inputRecord?.id,
|
|
185
|
+
inputRecord?.entityId,
|
|
186
|
+
inputRecord?.recordId,
|
|
187
|
+
inputEntity?.id
|
|
188
|
+
);
|
|
189
|
+
const organizationId = pickFirstIdentifier(
|
|
190
|
+
metadata?.organizationId,
|
|
191
|
+
resultRecord?.organizationId,
|
|
192
|
+
resultEntity?.organizationId,
|
|
193
|
+
inputRecord?.organizationId,
|
|
194
|
+
inputEntity?.organizationId,
|
|
195
|
+
ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null
|
|
196
|
+
);
|
|
197
|
+
const tenantId = pickFirstIdentifier(
|
|
198
|
+
metadata?.tenantId,
|
|
199
|
+
resultRecord?.tenantId,
|
|
200
|
+
resultEntity?.tenantId,
|
|
201
|
+
inputRecord?.tenantId,
|
|
202
|
+
inputEntity?.tenantId,
|
|
203
|
+
ctx.auth?.tenantId ?? null
|
|
204
|
+
);
|
|
205
|
+
const fallbackTenant = pickFirstIdentifier(metadata?.tenantId, ctx.auth?.tenantId ?? null);
|
|
206
|
+
const aliasSet = /* @__PURE__ */ new Set();
|
|
207
|
+
for (const alias of extractAliasList(metadata?.context ?? null)) {
|
|
208
|
+
aliasSet.add(alias);
|
|
209
|
+
}
|
|
210
|
+
const derived = deriveResourceFromCommandId(commandId);
|
|
211
|
+
if (derived) aliasSet.add(derived);
|
|
212
|
+
const aliasExtras = Array.from(aliasSet);
|
|
213
|
+
await invalidateCrudCache(
|
|
214
|
+
ctx.container,
|
|
215
|
+
resource,
|
|
216
|
+
{ id: recordId, organizationId, tenantId },
|
|
217
|
+
fallbackTenant,
|
|
218
|
+
`command:${commandId}:execute`,
|
|
219
|
+
aliasExtras
|
|
220
|
+
);
|
|
221
|
+
} catch (err) {
|
|
222
|
+
if (isCrudCacheDebugEnabled()) {
|
|
223
|
+
try {
|
|
224
|
+
console.debug("[crud][cache] execute-invalidation failed", { commandId, err });
|
|
225
|
+
} catch {
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
async invalidateCacheAfterUndo(log, ctx) {
|
|
231
|
+
const resource = typeof log.resourceKind === "string" ? log.resourceKind : null;
|
|
232
|
+
if (!resource) return;
|
|
233
|
+
try {
|
|
234
|
+
const recordId = pickFirstIdentifier(log.resourceId);
|
|
235
|
+
const organizationId = pickFirstIdentifier(log.organizationId, ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null);
|
|
236
|
+
const tenantId = pickFirstIdentifier(log.tenantId, ctx.auth?.tenantId ?? null);
|
|
237
|
+
const fallbackTenant = pickFirstIdentifier(log.tenantId, ctx.auth?.tenantId ?? null);
|
|
238
|
+
const aliasSet = /* @__PURE__ */ new Set();
|
|
239
|
+
for (const alias of extractAliasList(log.contextJson ?? null)) {
|
|
240
|
+
aliasSet.add(alias);
|
|
241
|
+
}
|
|
242
|
+
const derived = deriveResourceFromCommandId(log.commandId);
|
|
243
|
+
if (derived) aliasSet.add(derived);
|
|
244
|
+
const aliasExtras = Array.from(aliasSet);
|
|
245
|
+
await invalidateCrudCache(
|
|
246
|
+
ctx.container,
|
|
247
|
+
resource,
|
|
248
|
+
{ id: recordId, organizationId, tenantId },
|
|
249
|
+
fallbackTenant,
|
|
250
|
+
`command:${log.commandId}:undo`,
|
|
251
|
+
aliasExtras
|
|
252
|
+
);
|
|
253
|
+
} catch (err) {
|
|
254
|
+
if (isCrudCacheDebugEnabled()) {
|
|
255
|
+
try {
|
|
256
|
+
console.debug("[crud][cache] undo-invalidation failed", { commandId: log.commandId, err });
|
|
257
|
+
} catch {
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
async flushCrudSideEffects(container) {
|
|
263
|
+
try {
|
|
264
|
+
const dataEngine = container.resolve("dataEngine");
|
|
265
|
+
await dataEngine.flushOrmEntityChanges();
|
|
266
|
+
} catch {
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function wrapRedoPayload(existing, input) {
|
|
271
|
+
if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
|
|
272
|
+
const envelope = { __redoInput: input };
|
|
273
|
+
if (existing !== void 0) envelope.value = existing;
|
|
274
|
+
return envelope;
|
|
275
|
+
}
|
|
276
|
+
const current = existing;
|
|
277
|
+
if ("__redoInput" in current && current.__redoInput !== void 0) {
|
|
278
|
+
return current;
|
|
279
|
+
}
|
|
280
|
+
return { __redoInput: input, ...current };
|
|
281
|
+
}
|
|
282
|
+
export {
|
|
283
|
+
CommandBus
|
|
284
|
+
};
|
|
285
|
+
//# sourceMappingURL=command-bus.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 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 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'\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\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 = this.resolveHandler<TInput, TResult>(commandId)\n const snapshots = await this.prepareSnapshots(handler, options)\n const result = await handler.execute(options.input, options.ctx)\n const afterSnapshot = await this.captureAfter(handler, options, result)\n const snapshotsWithAfter = { ...snapshots, after: afterSnapshot }\n const logMeta = await this.buildLog(handler, options, result, snapshotsWithAfter)\n let mergedMeta = this.mergeMetadata(options.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 = options.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 const logEntry = await this.persistLog(commandId, options, mergedMeta)\n await this.invalidateCacheAfterExecute(commandId, options, result, mergedMeta)\n await this.flushCrudSideEffects(options.ctx.container)\n return { result, 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 = this.resolveHandler(log.commandId)\n if (!handler.undo || this.isUndoable(handler) === false) {\n throw new Error(`Command ${log.commandId} is not undoable`)\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)\n await this.invalidateCacheAfterUndo(log, ctx)\n await this.flushCrudSideEffects(ctx.container)\n }\n\n private resolveHandler<TInput, TResult>(commandId: string): CommandHandler<TInput, TResult> {\n const handler = commandRegistry.get<TInput, TResult>(commandId)\n if (!handler) throw new Error(`Command handler not registered for id ${commandId}`)\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 tenantId: primary?.tenantId ?? secondary?.tenantId ?? null,\n organizationId: primary?.organizationId ?? secondary?.organizationId ?? null,\n actorUserId: primary?.actorUserId ?? secondary?.actorUserId ?? null,\n actionLabel: primary?.actionLabel ?? secondary?.actionLabel ?? null,\n resourceKind: primary?.resourceKind ?? secondary?.resourceKind ?? null,\n resourceId: primary?.resourceId ?? secondary?.resourceId ?? null,\n undoToken: primary?.undoToken ?? secondary?.undoToken ?? null,\n payload: primary?.payload ?? secondary?.payload ?? null,\n snapshotBefore: primary?.snapshotBefore ?? secondary?.snapshotBefore ?? null,\n snapshotAfter: primary?.snapshotAfter ?? secondary?.snapshotAfter ?? null,\n changes: primary?.changes ?? secondary?.changes ?? null,\n context: primary?.context ?? secondary?.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 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 ('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 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 console.debug('[crud][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 console.debug('[crud][cache] undo-invalidation failed', { commandId: log.commandId, err })\n } catch {}\n }\n }\n }\n\n private async flushCrudSideEffects(container: AwilixContainer): Promise<void> {\n try {\n const dataEngine = (container.resolve('dataEngine') as DataEngine)\n await dataEngine.flushOrmEntityChanges()\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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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { loadCustomFieldValues } from "@open-mercato/shared/lib/crud/custom-fields";
|
|
2
|
+
async function loadCustomFieldSnapshot(em, { entityId, recordId, tenantId, organizationId, tenantFallbacks }) {
|
|
3
|
+
const tenant = tenantId ?? null;
|
|
4
|
+
const organization = organizationId ?? void 0;
|
|
5
|
+
const records = await loadCustomFieldValues({
|
|
6
|
+
em,
|
|
7
|
+
entityId,
|
|
8
|
+
recordIds: [recordId],
|
|
9
|
+
tenantIdByRecord: { [recordId]: tenant },
|
|
10
|
+
organizationIdByRecord: organization === void 0 ? void 0 : { [recordId]: organization ?? null },
|
|
11
|
+
tenantFallbacks: tenantFallbacks ?? [tenant]
|
|
12
|
+
});
|
|
13
|
+
const raw = records[recordId] ?? {};
|
|
14
|
+
const custom = {};
|
|
15
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
16
|
+
if (key.startsWith("cf_")) custom[key.slice(3)] = value;
|
|
17
|
+
}
|
|
18
|
+
return custom;
|
|
19
|
+
}
|
|
20
|
+
function buildCustomFieldResetMap(before, after) {
|
|
21
|
+
const values = {};
|
|
22
|
+
const keys = /* @__PURE__ */ new Set();
|
|
23
|
+
if (before) for (const key of Object.keys(before)) keys.add(key);
|
|
24
|
+
if (after) for (const key of Object.keys(after)) keys.add(key);
|
|
25
|
+
for (const key of keys) {
|
|
26
|
+
const hasBefore = Boolean(before && Object.prototype.hasOwnProperty.call(before, key));
|
|
27
|
+
if (hasBefore) {
|
|
28
|
+
const beforeValue = before?.[key];
|
|
29
|
+
if (beforeValue === null && Array.isArray(after?.[key])) {
|
|
30
|
+
values[key] = [];
|
|
31
|
+
} else {
|
|
32
|
+
values[key] = beforeValue;
|
|
33
|
+
}
|
|
34
|
+
} else {
|
|
35
|
+
values[key] = Array.isArray(after?.[key]) ? [] : null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return values;
|
|
39
|
+
}
|
|
40
|
+
function diffCustomFieldChanges(before, after) {
|
|
41
|
+
const out = {};
|
|
42
|
+
const keys = /* @__PURE__ */ new Set();
|
|
43
|
+
if (before) for (const key of Object.keys(before)) keys.add(key);
|
|
44
|
+
if (after) for (const key of Object.keys(after)) keys.add(key);
|
|
45
|
+
for (const key of keys) {
|
|
46
|
+
const prev = before ? before[key] : void 0;
|
|
47
|
+
const next = after ? after[key] : void 0;
|
|
48
|
+
if (!customFieldValuesEqual(prev, next)) {
|
|
49
|
+
out[key] = { from: prev ?? null, to: next ?? null };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
function customFieldValuesEqual(a, b) {
|
|
55
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
56
|
+
if (a.length !== b.length) return false;
|
|
57
|
+
return a.every((value, idx) => customFieldValuesEqual(value, b[idx]));
|
|
58
|
+
}
|
|
59
|
+
return a === b;
|
|
60
|
+
}
|
|
61
|
+
export {
|
|
62
|
+
buildCustomFieldResetMap,
|
|
63
|
+
diffCustomFieldChanges,
|
|
64
|
+
loadCustomFieldSnapshot
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=customFieldSnapshots.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/lib/commands/customFieldSnapshots.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\n\nexport type CustomFieldSnapshot = Record<string, unknown>\n\ntype LoadSnapshotOptions = {\n entityId: string\n recordId: string\n tenantId?: string | null\n organizationId?: string | null\n tenantFallbacks?: Array<string | null | undefined>\n}\n\nexport async function loadCustomFieldSnapshot(\n em: EntityManager,\n { entityId, recordId, tenantId, organizationId, tenantFallbacks }: LoadSnapshotOptions\n): Promise<CustomFieldSnapshot> {\n const tenant = tenantId ?? null\n const organization = organizationId ?? undefined\n const records = await loadCustomFieldValues({\n em,\n entityId: entityId as any,\n recordIds: [recordId],\n tenantIdByRecord: { [recordId]: tenant },\n organizationIdByRecord: organization === undefined ? undefined : { [recordId]: organization ?? null },\n tenantFallbacks: tenantFallbacks ?? [tenant],\n })\n const raw = records[recordId] ?? {}\n const custom: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(raw)) {\n if (key.startsWith('cf_')) custom[key.slice(3)] = value\n }\n return custom\n}\n\nexport function buildCustomFieldResetMap(\n before: CustomFieldSnapshot | undefined,\n after: CustomFieldSnapshot | undefined\n): Record<string, unknown> {\n const values: Record<string, unknown> = {}\n const keys = new Set<string>()\n if (before) for (const key of Object.keys(before)) keys.add(key)\n if (after) for (const key of Object.keys(after)) keys.add(key)\n for (const key of keys) {\n const hasBefore = Boolean(before && Object.prototype.hasOwnProperty.call(before, key))\n if (hasBefore) {\n const beforeValue = before?.[key]\n if (beforeValue === null && Array.isArray(after?.[key])) {\n values[key] = []\n } else {\n values[key] = beforeValue\n }\n } else {\n values[key] = Array.isArray(after?.[key]) ? [] : null\n }\n }\n return values\n}\n\nexport type CustomFieldChangeSet = Record<string, { from: unknown; to: unknown }>\n\nexport function diffCustomFieldChanges(\n before: CustomFieldSnapshot | undefined,\n after: CustomFieldSnapshot | undefined\n): CustomFieldChangeSet {\n const out: CustomFieldChangeSet = {}\n const keys = new Set<string>()\n if (before) for (const key of Object.keys(before)) keys.add(key)\n if (after) for (const key of Object.keys(after)) keys.add(key)\n for (const key of keys) {\n const prev = before ? before[key] : undefined\n const next = after ? after[key] : undefined\n if (!customFieldValuesEqual(prev, next)) {\n out[key] = { from: prev ?? null, to: next ?? null }\n }\n }\n return out\n}\n\nfunction customFieldValuesEqual(a: unknown, b: unknown): boolean {\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false\n return a.every((value, idx) => customFieldValuesEqual(value, b[idx]))\n }\n return a === b\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,6BAA6B;AAYtC,eAAsB,wBACpB,IACA,EAAE,UAAU,UAAU,UAAU,gBAAgB,gBAAgB,GAClC;AAC9B,QAAM,SAAS,YAAY;AAC3B,QAAM,eAAe,kBAAkB;AACvC,QAAM,UAAU,MAAM,sBAAsB;AAAA,IAC1C;AAAA,IACA;AAAA,IACA,WAAW,CAAC,QAAQ;AAAA,IACpB,kBAAkB,EAAE,CAAC,QAAQ,GAAG,OAAO;AAAA,IACvC,wBAAwB,iBAAiB,SAAY,SAAY,EAAE,CAAC,QAAQ,GAAG,gBAAgB,KAAK;AAAA,IACpG,iBAAiB,mBAAmB,CAAC,MAAM;AAAA,EAC7C,CAAC;AACD,QAAM,MAAM,QAAQ,QAAQ,KAAK,CAAC;AAClC,QAAM,SAAkC,CAAC;AACzC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAAG,GAAG;AAC9C,QAAI,IAAI,WAAW,KAAK,EAAG,QAAO,IAAI,MAAM,CAAC,CAAC,IAAI;AAAA,EACpD;AACA,SAAO;AACT;AAEO,SAAS,yBACd,QACA,OACyB;AACzB,QAAM,SAAkC,CAAC;AACzC,QAAM,OAAO,oBAAI,IAAY;AAC7B,MAAI,OAAQ,YAAW,OAAO,OAAO,KAAK,MAAM,EAAG,MAAK,IAAI,GAAG;AAC/D,MAAI,MAAO,YAAW,OAAO,OAAO,KAAK,KAAK,EAAG,MAAK,IAAI,GAAG;AAC7D,aAAW,OAAO,MAAM;AACtB,UAAM,YAAY,QAAQ,UAAU,OAAO,UAAU,eAAe,KAAK,QAAQ,GAAG,CAAC;AACrF,QAAI,WAAW;AACb,YAAM,cAAc,SAAS,GAAG;AAChC,UAAI,gBAAgB,QAAQ,MAAM,QAAQ,QAAQ,GAAG,CAAC,GAAG;AACvD,eAAO,GAAG,IAAI,CAAC;AAAA,MACjB,OAAO;AACL,eAAO,GAAG,IAAI;AAAA,MAChB;AAAA,IACF,OAAO;AACL,aAAO,GAAG,IAAI,MAAM,QAAQ,QAAQ,GAAG,CAAC,IAAI,CAAC,IAAI;AAAA,IACnD;AAAA,EACF;AACA,SAAO;AACT;AAIO,SAAS,uBACd,QACA,OACsB;AACtB,QAAM,MAA4B,CAAC;AACnC,QAAM,OAAO,oBAAI,IAAY;AAC7B,MAAI,OAAQ,YAAW,OAAO,OAAO,KAAK,MAAM,EAAG,MAAK,IAAI,GAAG;AAC/D,MAAI,MAAO,YAAW,OAAO,OAAO,KAAK,KAAK,EAAG,MAAK,IAAI,GAAG;AAC7D,aAAW,OAAO,MAAM;AACtB,UAAM,OAAO,SAAS,OAAO,GAAG,IAAI;AACpC,UAAM,OAAO,QAAQ,MAAM,GAAG,IAAI;AAClC,QAAI,CAAC,uBAAuB,MAAM,IAAI,GAAG;AACvC,UAAI,GAAG,IAAI,EAAE,MAAM,QAAQ,MAAM,IAAI,QAAQ,KAAK;AAAA,IACpD;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,uBAAuB,GAAY,GAAqB;AAC/D,MAAI,MAAM,QAAQ,CAAC,KAAK,MAAM,QAAQ,CAAC,GAAG;AACxC,QAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,WAAO,EAAE,MAAM,CAAC,OAAO,QAAQ,uBAAuB,OAAO,EAAE,GAAG,CAAC,CAAC;AAAA,EACtE;AACA,SAAO,MAAM;AACf;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { splitCustomFieldPayload } from "@open-mercato/shared/lib/crud/custom-fields";
|
|
2
|
+
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
3
|
+
import { normalizeCustomFieldValues } from "../custom-fields/normalize.js";
|
|
4
|
+
import { normalizeCustomFieldValues as normalizeCustomFieldValues2 } from "../custom-fields/normalize.js";
|
|
5
|
+
function parseWithCustomFields(schema, raw) {
|
|
6
|
+
const { base, custom } = splitCustomFieldPayload(raw);
|
|
7
|
+
const parsed = schema.parse(base);
|
|
8
|
+
return { parsed, custom };
|
|
9
|
+
}
|
|
10
|
+
async function setCustomFieldsIfAny(opts) {
|
|
11
|
+
const { values } = opts;
|
|
12
|
+
if (!values || !Object.keys(values).length) return;
|
|
13
|
+
const { dataEngine, entityId, recordId, tenantId, organizationId, notify = false } = opts;
|
|
14
|
+
const normalized = normalizeCustomFieldValues(values);
|
|
15
|
+
await dataEngine.setCustomFields({
|
|
16
|
+
entityId,
|
|
17
|
+
recordId,
|
|
18
|
+
tenantId,
|
|
19
|
+
organizationId,
|
|
20
|
+
values: normalized,
|
|
21
|
+
notify
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
async function emitCrudSideEffects(opts) {
|
|
25
|
+
const { dataEngine, action, entity, identifiers, events, indexer } = opts;
|
|
26
|
+
dataEngine.markOrmEntityChange({
|
|
27
|
+
action,
|
|
28
|
+
entity,
|
|
29
|
+
identifiers,
|
|
30
|
+
events,
|
|
31
|
+
indexer
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async function emitCrudUndoSideEffects(opts) {
|
|
35
|
+
const { dataEngine, action, entity, identifiers, events, indexer } = opts;
|
|
36
|
+
if (!entity) return;
|
|
37
|
+
dataEngine.markOrmEntityChange({
|
|
38
|
+
action,
|
|
39
|
+
entity,
|
|
40
|
+
identifiers,
|
|
41
|
+
events,
|
|
42
|
+
indexer
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
async function flushCrudSideEffects(dataEngine) {
|
|
46
|
+
await dataEngine.flushOrmEntityChanges();
|
|
47
|
+
}
|
|
48
|
+
function buildChanges(before, after, keys) {
|
|
49
|
+
if (!before) return {};
|
|
50
|
+
const diff = {};
|
|
51
|
+
for (const key of keys) {
|
|
52
|
+
const prev = before[key];
|
|
53
|
+
const next = after[key];
|
|
54
|
+
if (prev !== next) diff[key] = { from: prev, to: next };
|
|
55
|
+
}
|
|
56
|
+
return diff;
|
|
57
|
+
}
|
|
58
|
+
function requireTenantScope(authTenantId, requested) {
|
|
59
|
+
if (authTenantId && requested && requested !== authTenantId) {
|
|
60
|
+
throw new CrudHttpError(403, { error: "Forbidden" });
|
|
61
|
+
}
|
|
62
|
+
const tenantId = requested || authTenantId;
|
|
63
|
+
if (!tenantId) throw new CrudHttpError(400, { error: "Tenant scope required" });
|
|
64
|
+
return tenantId;
|
|
65
|
+
}
|
|
66
|
+
function requireId(value, message = "ID is required") {
|
|
67
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
68
|
+
if (typeof value === "number" || typeof value === "bigint") return String(value);
|
|
69
|
+
if (value && typeof value === "object") {
|
|
70
|
+
const source = value;
|
|
71
|
+
const candidates = [
|
|
72
|
+
source.id,
|
|
73
|
+
source.recordId,
|
|
74
|
+
isRecord(source.body) ? source.body.id : void 0,
|
|
75
|
+
isRecord(source.query) ? source.query.id : void 0
|
|
76
|
+
];
|
|
77
|
+
for (const candidate of candidates) {
|
|
78
|
+
if (typeof candidate === "string" && candidate.trim()) return candidate;
|
|
79
|
+
if (typeof candidate === "number" || typeof candidate === "bigint") return String(candidate);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
throw new CrudHttpError(400, { error: message });
|
|
83
|
+
}
|
|
84
|
+
function isRecord(input) {
|
|
85
|
+
return !!input && typeof input === "object";
|
|
86
|
+
}
|
|
87
|
+
export {
|
|
88
|
+
buildChanges,
|
|
89
|
+
emitCrudSideEffects,
|
|
90
|
+
emitCrudUndoSideEffects,
|
|
91
|
+
flushCrudSideEffects,
|
|
92
|
+
normalizeCustomFieldValues2 as normalizeCustomFieldValues,
|
|
93
|
+
parseWithCustomFields,
|
|
94
|
+
requireId,
|
|
95
|
+
requireTenantScope,
|
|
96
|
+
setCustomFieldsIfAny
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=helpers.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/lib/commands/helpers.ts"],
|
|
4
|
+
"sourcesContent": ["import { splitCustomFieldPayload } from '@open-mercato/shared/lib/crud/custom-fields'\nimport type { z } from 'zod'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { DataEngine } from '@open-mercato/shared/lib/data/engine'\nimport { normalizeCustomFieldValues } from '../custom-fields/normalize'\nexport { normalizeCustomFieldValues } from '../custom-fields/normalize'\nimport type { CrudEventsConfig, CrudIndexerConfig, CrudEmitContext } from '@open-mercato/shared/lib/crud/types'\nimport type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport type { CommandLogMetadata } from '@open-mercato/shared/lib/commands'\n\nexport type ParsedPayload<TSchema extends z.ZodTypeAny> = {\n parsed: z.infer<TSchema>\n custom: Record<string, unknown>\n}\n\nexport function parseWithCustomFields<TSchema extends z.ZodTypeAny>(\n schema: TSchema,\n raw: unknown\n): ParsedPayload<TSchema> {\n const { base, custom } = splitCustomFieldPayload(raw)\n const parsed = schema.parse(base)\n return { parsed, custom }\n}\n\nexport async function setCustomFieldsIfAny(opts: {\n dataEngine: DataEngine\n entityId: string\n recordId: string\n tenantId: string | null\n organizationId: string | null\n values: Record<string, unknown>\n notify?: boolean\n}) {\n const { values } = opts\n if (!values || !Object.keys(values).length) return\n const { dataEngine, entityId, recordId, tenantId, organizationId, notify = false } = opts\n const normalized = normalizeCustomFieldValues(values)\n await dataEngine.setCustomFields({\n entityId,\n recordId,\n tenantId,\n organizationId,\n values: normalized,\n notify,\n })\n}\n\nexport async function emitCrudSideEffects<TEntity>(opts: {\n dataEngine: DataEngine\n action: 'created' | 'updated' | 'deleted'\n entity: TEntity\n identifiers: CrudEmitContext<TEntity>['identifiers']\n events?: CrudEventsConfig<TEntity>\n indexer?: CrudIndexerConfig<TEntity>\n}) {\n const { dataEngine, action, entity, identifiers, events, indexer } = opts\n dataEngine.markOrmEntityChange({\n action,\n entity,\n identifiers,\n events,\n indexer,\n })\n}\n\nexport async function emitCrudUndoSideEffects<TEntity>(opts: {\n dataEngine: DataEngine\n action: 'created' | 'updated' | 'deleted'\n entity: TEntity | null | undefined\n identifiers: CrudEmitContext<TEntity>['identifiers']\n events?: CrudEventsConfig<TEntity>\n indexer?: CrudIndexerConfig<TEntity>\n}) {\n const { dataEngine, action, entity, identifiers, events, indexer } = opts\n if (!entity) return\n dataEngine.markOrmEntityChange({\n action,\n entity,\n identifiers,\n events,\n indexer,\n })\n}\n\nexport async function flushCrudSideEffects(dataEngine: DataEngine): Promise<void> {\n await dataEngine.flushOrmEntityChanges()\n}\n\nexport function buildChanges(\n before: Record<string, unknown> | null | undefined,\n after: Record<string, unknown>,\n keys: readonly string[]\n): Record<string, { from: unknown; to: unknown }> {\n if (!before) return {}\n const diff: Record<string, { from: unknown; to: unknown }> = {}\n for (const key of keys) {\n const prev = before[key]\n const next = after[key]\n if (prev !== next) diff[key] = { from: prev, to: next }\n }\n return diff\n}\n\nexport function requireTenantScope(authTenantId: string | null, requested?: string | null): string {\n if (authTenantId && requested && requested !== authTenantId) {\n throw new CrudHttpError(403, { error: 'Forbidden' })\n }\n const tenantId = requested || authTenantId\n if (!tenantId) throw new CrudHttpError(400, { error: 'Tenant scope required' })\n return tenantId\n}\n\nexport function requireId(value: unknown, message = 'ID is required'): string {\n if (typeof value === 'string' && value.trim()) return value\n if (typeof value === 'number' || typeof value === 'bigint') return String(value)\n if (value && typeof value === 'object') {\n const source = value as Record<string, unknown>\n const candidates: unknown[] = [\n source.id,\n source.recordId,\n isRecord(source.body) ? source.body.id : undefined,\n isRecord(source.query) ? source.query.id : undefined,\n ]\n for (const candidate of candidates) {\n if (typeof candidate === 'string' && candidate.trim()) return candidate\n if (typeof candidate === 'number' || typeof candidate === 'bigint') return String(candidate)\n }\n }\n throw new CrudHttpError(400, { error: message })\n}\n\nfunction isRecord(input: unknown): input is { [key: string]: unknown } {\n return !!input && typeof input === 'object'\n}\n\nexport type LogBuilderArgs<TInput, TResult> = {\n input: TInput\n result: TResult\n ctx: CommandRuntimeContext\n snapshots: { before?: unknown; after?: unknown }\n}\n\nexport type LogBuilder<TInput, TResult> = (args: LogBuilderArgs<TInput, TResult>) => CommandLogMetadata | null | Promise<CommandLogMetadata | null>\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,+BAA+B;AAExC,SAAS,qBAAqB;AAE9B,SAAS,kCAAkC;AAC3C,SAAS,8BAAAA,mCAAkC;AAUpC,SAAS,sBACd,QACA,KACwB;AACxB,QAAM,EAAE,MAAM,OAAO,IAAI,wBAAwB,GAAG;AACpD,QAAM,SAAS,OAAO,MAAM,IAAI;AAChC,SAAO,EAAE,QAAQ,OAAO;AAC1B;AAEA,eAAsB,qBAAqB,MAQxC;AACD,QAAM,EAAE,OAAO,IAAI;AACnB,MAAI,CAAC,UAAU,CAAC,OAAO,KAAK,MAAM,EAAE,OAAQ;AAC5C,QAAM,EAAE,YAAY,UAAU,UAAU,UAAU,gBAAgB,SAAS,MAAM,IAAI;AACrF,QAAM,aAAa,2BAA2B,MAAM;AACpD,QAAM,WAAW,gBAAgB;AAAA,IAC/B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,oBAA6B,MAOhD;AACD,QAAM,EAAE,YAAY,QAAQ,QAAQ,aAAa,QAAQ,QAAQ,IAAI;AACrE,aAAW,oBAAoB;AAAA,IAC7B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,wBAAiC,MAOpD;AACD,QAAM,EAAE,YAAY,QAAQ,QAAQ,aAAa,QAAQ,QAAQ,IAAI;AACrE,MAAI,CAAC,OAAQ;AACb,aAAW,oBAAoB;AAAA,IAC7B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACH;AAEA,eAAsB,qBAAqB,YAAuC;AAChF,QAAM,WAAW,sBAAsB;AACzC;AAEO,SAAS,aACd,QACA,OACA,MACgD;AAChD,MAAI,CAAC,OAAQ,QAAO,CAAC;AACrB,QAAM,OAAuD,CAAC;AAC9D,aAAW,OAAO,MAAM;AACtB,UAAM,OAAO,OAAO,GAAG;AACvB,UAAM,OAAO,MAAM,GAAG;AACtB,QAAI,SAAS,KAAM,MAAK,GAAG,IAAI,EAAE,MAAM,MAAM,IAAI,KAAK;AAAA,EACxD;AACA,SAAO;AACT;AAEO,SAAS,mBAAmB,cAA6B,WAAmC;AACjG,MAAI,gBAAgB,aAAa,cAAc,cAAc;AAC3D,UAAM,IAAI,cAAc,KAAK,EAAE,OAAO,YAAY,CAAC;AAAA,EACrD;AACA,QAAM,WAAW,aAAa;AAC9B,MAAI,CAAC,SAAU,OAAM,IAAI,cAAc,KAAK,EAAE,OAAO,wBAAwB,CAAC;AAC9E,SAAO;AACT;AAEO,SAAS,UAAU,OAAgB,UAAU,kBAA0B;AAC5E,MAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAG,QAAO;AACtD,MAAI,OAAO,UAAU,YAAY,OAAO,UAAU,SAAU,QAAO,OAAO,KAAK;AAC/E,MAAI,SAAS,OAAO,UAAU,UAAU;AACtC,UAAM,SAAS;AACf,UAAM,aAAwB;AAAA,MAC5B,OAAO;AAAA,MACP,OAAO;AAAA,MACP,SAAS,OAAO,IAAI,IAAI,OAAO,KAAK,KAAK;AAAA,MACzC,SAAS,OAAO,KAAK,IAAI,OAAO,MAAM,KAAK;AAAA,IAC7C;AACA,eAAW,aAAa,YAAY;AAClC,UAAI,OAAO,cAAc,YAAY,UAAU,KAAK,EAAG,QAAO;AAC9D,UAAI,OAAO,cAAc,YAAY,OAAO,cAAc,SAAU,QAAO,OAAO,SAAS;AAAA,IAC7F;AAAA,EACF;AACA,QAAM,IAAI,cAAc,KAAK,EAAE,OAAO,QAAQ,CAAC;AACjD;AAEA,SAAS,SAAS,OAAqD;AACrE,SAAO,CAAC,CAAC,SAAS,OAAO,UAAU;AACrC;",
|
|
6
|
+
"names": ["normalizeCustomFieldValues"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/lib/commands/index.ts"],
|
|
4
|
+
"sourcesContent": ["export * from './types'\nexport * from './registry'\nexport { CommandBus } from './command-bus'\nexport * from './customFieldSnapshots'\n"],
|
|
5
|
+
"mappings": "AAAA,cAAc;AACd,cAAc;AACd,SAAS,kBAAkB;AAC3B,cAAc;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const HEADER_PREFIX = "omop:";
|
|
2
|
+
function serializeOperationMetadata(payload) {
|
|
3
|
+
const encoded = encodeURIComponent(JSON.stringify(payload));
|
|
4
|
+
return `${HEADER_PREFIX}${encoded}`;
|
|
5
|
+
}
|
|
6
|
+
function deserializeOperationMetadata(value) {
|
|
7
|
+
if (!value || typeof value !== "string") return null;
|
|
8
|
+
const trimmed = value.startsWith(HEADER_PREFIX) ? value.slice(HEADER_PREFIX.length) : value;
|
|
9
|
+
try {
|
|
10
|
+
const parsed = JSON.parse(decodeURIComponent(trimmed));
|
|
11
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
12
|
+
if (typeof parsed.id !== "string" || typeof parsed.commandId !== "string") return null;
|
|
13
|
+
if (typeof parsed.undoToken !== "string" || !parsed.undoToken) return null;
|
|
14
|
+
if (typeof parsed.executedAt !== "string") return null;
|
|
15
|
+
return {
|
|
16
|
+
id: parsed.id,
|
|
17
|
+
undoToken: parsed.undoToken,
|
|
18
|
+
commandId: parsed.commandId,
|
|
19
|
+
actionLabel: parsed.actionLabel ?? null,
|
|
20
|
+
resourceKind: parsed.resourceKind ?? null,
|
|
21
|
+
resourceId: parsed.resourceId ?? null,
|
|
22
|
+
executedAt: parsed.executedAt
|
|
23
|
+
};
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
deserializeOperationMetadata,
|
|
30
|
+
serializeOperationMetadata
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=operationMetadata.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/lib/commands/operationMetadata.ts"],
|
|
4
|
+
"sourcesContent": ["export type OperationMetadataPayload = {\n id: string\n undoToken: string\n commandId: string\n actionLabel: string | null\n resourceKind: string | null\n resourceId: string | null\n executedAt: string\n}\n\nconst HEADER_PREFIX = 'omop:'\n\nexport function serializeOperationMetadata(payload: OperationMetadataPayload): string {\n const encoded = encodeURIComponent(JSON.stringify(payload))\n return `${HEADER_PREFIX}${encoded}`\n}\n\nexport function deserializeOperationMetadata(value: string | null | undefined): OperationMetadataPayload | null {\n if (!value || typeof value !== 'string') return null\n const trimmed = value.startsWith(HEADER_PREFIX) ? value.slice(HEADER_PREFIX.length) : value\n try {\n const parsed = JSON.parse(decodeURIComponent(trimmed))\n if (!parsed || typeof parsed !== 'object') return null\n if (typeof parsed.id !== 'string' || typeof parsed.commandId !== 'string') return null\n if (typeof parsed.undoToken !== 'string' || !parsed.undoToken) return null\n if (typeof parsed.executedAt !== 'string') return null\n return {\n id: parsed.id,\n undoToken: parsed.undoToken,\n commandId: parsed.commandId,\n actionLabel: parsed.actionLabel ?? null,\n resourceKind: parsed.resourceKind ?? null,\n resourceId: parsed.resourceId ?? null,\n executedAt: parsed.executedAt,\n }\n } catch {\n return null\n }\n}\n\n"],
|
|
5
|
+
"mappings": "AAUA,MAAM,gBAAgB;AAEf,SAAS,2BAA2B,SAA2C;AACpF,QAAM,UAAU,mBAAmB,KAAK,UAAU,OAAO,CAAC;AAC1D,SAAO,GAAG,aAAa,GAAG,OAAO;AACnC;AAEO,SAAS,6BAA6B,OAAmE;AAC9G,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,UAAU,MAAM,WAAW,aAAa,IAAI,MAAM,MAAM,cAAc,MAAM,IAAI;AACtF,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,mBAAmB,OAAO,CAAC;AACrD,QAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAClD,QAAI,OAAO,OAAO,OAAO,YAAY,OAAO,OAAO,cAAc,SAAU,QAAO;AAClF,QAAI,OAAO,OAAO,cAAc,YAAY,CAAC,OAAO,UAAW,QAAO;AACtE,QAAI,OAAO,OAAO,eAAe,SAAU,QAAO;AAClD,WAAO;AAAA,MACL,IAAI,OAAO;AAAA,MACX,WAAW,OAAO;AAAA,MAClB,WAAW,OAAO;AAAA,MAClB,aAAa,OAAO,eAAe;AAAA,MACnC,cAAc,OAAO,gBAAgB;AAAA,MACrC,YAAY,OAAO,cAAc;AAAA,MACjC,YAAY,OAAO;AAAA,IACrB;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
class CommandRegistry {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.handlers = /* @__PURE__ */ new Map();
|
|
4
|
+
}
|
|
5
|
+
register(handler) {
|
|
6
|
+
if (!handler?.id) throw new Error("Command handler must define an id");
|
|
7
|
+
if (this.handlers.has(handler.id)) {
|
|
8
|
+
throw new Error(`Duplicate command registration for id ${handler.id}`);
|
|
9
|
+
}
|
|
10
|
+
this.handlers.set(handler.id, handler);
|
|
11
|
+
}
|
|
12
|
+
unregister(id) {
|
|
13
|
+
this.handlers.delete(id);
|
|
14
|
+
}
|
|
15
|
+
get(id) {
|
|
16
|
+
return this.handlers.get(id) ?? null;
|
|
17
|
+
}
|
|
18
|
+
has(id) {
|
|
19
|
+
return this.handlers.has(id);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* List all registered command handler IDs.
|
|
23
|
+
*/
|
|
24
|
+
list() {
|
|
25
|
+
return Array.from(this.handlers.keys());
|
|
26
|
+
}
|
|
27
|
+
clear() {
|
|
28
|
+
this.handlers.clear();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const commandRegistry = new CommandRegistry();
|
|
32
|
+
function registerCommand(handler) {
|
|
33
|
+
commandRegistry.register(handler);
|
|
34
|
+
}
|
|
35
|
+
function unregisterCommand(id) {
|
|
36
|
+
commandRegistry.unregister(id);
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
commandRegistry,
|
|
40
|
+
registerCommand,
|
|
41
|
+
unregisterCommand
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=registry.js.map
|