@open-mercato/shared 0.8.1-develop.7452.1.5e211edd51 → 0.8.1-develop.7455.1.8f7e53badb

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/commands/types.ts"],
4
- "sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { ZodTypeAny } from 'zod'\nimport { randomUUID } from 'crypto'\nimport type { AuthContext } from '../auth/server'\nimport type { OrganizationScope } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { TransactionLifetime } from './transaction-lifetime'\n\n/**\n * Bulk-import / backfill deferral flags. When a command runs under a context that\n * carries this, the command bus and data engine suppress the heavy per-record side\n * effects flagged below so a large backfill can defer them to a single batched pass.\n *\n * IMPORTANT \u2014 the caller owns restoring whatever it suppresses. With `skipReindex`\n * the `query_index` projection (and its search tokens) is stale for every record\n * written under this context until the caller runs a batched `query_index rebuild`\n * for the affected entity types at end-of-run.\n *\n * Concurrency: these flags are read from the context and threaded as a local\n * parameter through the side-effect flush \u2014 no shared engine state is mutated \u2014 so\n * two commands running concurrently with different flags never clobber each other.\n */\nexport type BulkImportSuppression = {\n /** Skip the inline `query_index.upsert_one` / `delete_one` reindex (rebuild after the run). */\n skipReindex?: boolean\n /** Skip the per-record `<module>.<entity>.<action>` domain event emission. */\n skipEvents?: boolean\n /** Advisory: handlers that fan out per-record notifications SHOULD honor this and skip them. */\n skipNotifications?: boolean\n /**\n * Skip a command's own full-collection derived-data rebuild (e.g. category tree\n * `parentId`/`treePath`/`depth`/ancestor-descendant rewrite). The caller MUST run that rebuild\n * itself exactly once after the batch, in a `finally` that covers the completed, cancelled and\n * failed paths \u2014 otherwise every record written under this context is left with a stale\n * derived shape.\n */\n skipDerivedRebuild?: boolean\n}\n\nexport type CommandRuntimeContext = {\n container: AwilixContainer\n auth: AuthContext | null\n organizationScope: OrganizationScope | null\n selectedOrganizationId: string | null\n organizationIds: string[] | null\n request?: Request\n syncOrigin?: string | null\n /**\n * See {@link BulkImportSuppression}. Set by bulk backfill callers to defer heavy\n * per-record side effects (reindex, events, notifications). The caller MUST rebuild\n * the `query_index` for the affected entity types after the run when `skipReindex`\n * is set. Unset for normal (interactive) writes \u2014 they get all side effects.\n */\n bulkImport?: BulkImportSuppression\n /**\n * Marks a trusted server-side invocation (CLI seeding, tenant setup) that runs\n * without an authenticated end-user actor. Commands that gate writes behind a\n * privileged actor (e.g. super-admin-only platform tables) may treat this as\n * an explicit system grant. HTTP request paths MUST NOT set this \u2014 they always\n * carry a real `auth` actor, so a present-but-unprivileged actor stays denied.\n */\n systemActor?: boolean\n /**\n * When set, command handlers that support it MUST run their writes within this\n * existing transactional EntityManager (reusing its row locks) instead of\n * opening their own transaction. Lets a caller compose a command with its own\n * surrounding work as a single atomic, single-locked operation.\n */\n transactionalEm?: EntityManager\n /**\n * Identifies the owner of an already-active {@link transactionalEm} lifetime.\n * Atomic replay accepts an ambient EntityManager only when this is the exact\n * token returned by `getTransactionLifetime(transactionalEm)` from the outer\n * `withAtomicFlush` phase. The owner completes the token after its real commit\n * or rollback, allowing replay leases and post-commit work to follow that\n * boundary. Omit when the command bus opens and owns the transaction itself.\n */\n transactionLifetime?: TransactionLifetime\n /**\n * Optional request-level replay authorization that the command bus binds to\n * its replay EntityManager. Atomic handlers run this guard in the same\n * transaction as the source-log transition and domain mutation; legacy\n * handlers run it in a dedicated read transaction before preserving their\n * existing replay lifecycle.\n */\n replayTransactionGuard?: (args: {\n operation: CommandReplayOperation\n logEntry: CommandUndoLogEntry\n transactionalEm: EntityManager\n }) => Promise<void> | void\n /**\n * On-behalf-of attribution for non-human principals (Agent Identity &\n * On-Behalf-Of, Wave 4 P2). When an agent runs on behalf of a human, the\n * orchestrator's `runAs` wrapper sets this so every `ActionLog` the command\n * path writes records `actorUserId = runAs.actorUserId` (the agent principal's\n * `auth.User` id), `onBehalfOfUserId = runAs.onBehalfOfUserId` (the invoking\n * human, or null for system-invoked agents), and `sourceKey = runAs.source`\n * (`'agent'`). Additive + optional: callers that omit it keep the existing\n * `ctx.auth.sub`-derived attribution unchanged. This threads agent attribution\n * through the SAME audited Command/CRUD path as a human action \u2014 not a parallel\n * audit path.\n */\n runAs?: CommandRunAsContext\n}\n\nexport type CommandRunAsContext = {\n /** The actor stamped on every ActionLog this context produces (agent `auth.User` id). */\n actorUserId: string\n /** The human (or system) principal the actor acts on behalf of; null when system-invoked. */\n onBehalfOfUserId?: string | null\n /** The audit source key for the attributed writes; `'agent'` for agent runs. */\n source: 'agent'\n}\n\nexport type CommandLogMetadata = {\n skipLog?: boolean\n /**\n * Per-execution replay policy. `false` records the audit entry without an undo\n * token or command payload, so neither undo nor redo can replay sensitive input.\n * Omitted preserves the command handler's existing replay behavior.\n */\n replayable?: boolean\n /**\n * Overrides the command input persisted in the audit entry's redo envelope.\n * The default is the raw input, which for credential-bearing commands would put\n * a plaintext secret at rest in `action_logs`. A handler that still wants its\n * operation to be undoable supplies a redacted projection here instead of\n * switching the whole entry to `replayable: false` \u2014 suppressing the undo token\n * removes the operator's ability to revert a write that carries no secret of its\n * own (undoing a user create only deletes a row).\n */\n redoInput?: unknown\n tenantId?: string | null\n organizationId?: string | null\n actorUserId?: string | null\n onBehalfOfUserId?: string | null\n actionLabel?: string | null\n resourceKind?: string | null\n resourceId?: string | null\n parentResourceKind?: string | null\n parentResourceId?: string | null\n undoToken?: string | null\n payload?: unknown\n snapshotBefore?: unknown\n snapshotAfter?: unknown\n relatedResourceKind?: string | null\n relatedResourceId?: string | null\n changes?: Record<string, unknown> | null\n context?: Record<string, unknown> | null\n}\n\nexport type CommandExecuteResult<TResult> = {\n result: TResult\n logEntry: any | null\n /**\n * True when an atomic redo finalized its source action log in the same\n * transaction as the domain mutation and the newly persisted log entry, and\n * that transaction has committed. An ambient replay returns this as false\n * while its caller-owned transaction is still active; the returned result\n * object is updated to true by the owner's commit callback. It remains false\n * on rollback. Callers that historically finalized redo themselves can use\n * this additive signal to avoid a redundant post-commit write.\n */\n replaySourceFinalized?: boolean\n}\n\n/**\n * Shape of the persisted action log handed to a command's `undo()` handler.\n *\n * IMPORTANT: there is intentionally **no `payload` field**. `buildLog()` returns\n * a `payload` in its metadata, but the command bus persists that under\n * `commandPayload` (column `command_payload`, wrapped in a redo envelope) \u2014 the\n * stored row never has a top-level `payload`. Reading `logEntry.payload` in an\n * undo handler is therefore always `undefined` and silently no-ops the undo\n * (issue #2504). Always read the undo snapshot through\n * `extractUndoPayload(logEntry)` from `@open-mercato/shared/lib/commands/undo`,\n * which unwraps `commandPayload`/snapshots correctly. Omitting `payload` here\n * makes the footgun a compile-time error instead of a runtime silent failure.\n */\nexport type CommandUndoLogEntry = {\n id?: string\n commandId?: string\n commandPayload?: unknown | null\n snapshotBefore?: unknown | null\n snapshotAfter?: unknown | null\n resourceKind?: string | null\n resourceId?: string | null\n undoToken?: string | null\n actionLabel?: string | null\n tenantId?: string | null\n organizationId?: string | null\n actorUserId?: string | null\n changesJson?: Record<string, unknown> | null\n contextJson?: Record<string, unknown> | null\n createdAt?: Date | string\n updatedAt?: Date | string\n}\n\nexport type CommandLogBuilderArgs<TInput, TResult> = {\n input: TInput\n result: TResult\n ctx: CommandRuntimeContext\n snapshots: {\n before?: unknown\n after?: unknown\n }\n}\n\nexport type CommandReplayOperation = 'undo' | 'redo'\n\nexport type CommandReplayAuthorizationArgs<TInput> = {\n operation: CommandReplayOperation\n input: TInput\n ctx: CommandRuntimeContext\n logEntry: CommandUndoLogEntry\n}\n\nexport interface CommandHandler<TInput = unknown, TResult = unknown> {\n readonly id: string\n readonly isUndoable?: boolean\n /**\n * Opts replay into the command bus's transaction-bound lifecycle. The source\n * action-log transition, handler mutation, and any new replay log share one\n * EntityManager and commit or roll back together. Handlers that enable this\n * MUST reuse `ctx.transactionalEm` for every replay-time database operation.\n * Omitted preserves the legacy non-atomic replay path.\n */\n readonly atomicReplay?: boolean\n /**\n * Acquires every authorization-state lock needed by an atomic replay before\n * request-level replay authorization and feature-gated interceptors run.\n * Implementations MUST discover their complete actor/target/destination set\n * before taking the first lock and MUST reuse `ctx.transactionalEm`.\n * Omitted preserves the existing lifecycle for handlers that do not need\n * domain-specific authorization stabilization.\n */\n stabilizeReplay?(params: CommandReplayAuthorizationArgs<TInput>): Promise<void> | void\n /**\n * Optional Zod schema describing the command's return value. Feeds the\n * workflows context ledger so downstream activities can reason about the\n * shape a command produces; when absent the ledger renders the output as\n * unknown.\n */\n readonly outputSchema?: ZodTypeAny\n prepare?(input: TInput, ctx: CommandRuntimeContext): Promise<{ before?: unknown } | null> | { before?: unknown } | null\n execute(input: TInput, ctx: CommandRuntimeContext): Promise<TResult> | TResult\n buildLog?(args: CommandLogBuilderArgs<TInput, TResult>): Promise<CommandLogMetadata | null | undefined> | CommandLogMetadata | null | undefined\n captureAfter?(input: TInput, result: TResult, ctx: CommandRuntimeContext): Promise<unknown> | unknown\n /**\n * Re-authorizes a stored command against the actor and resource state that\n * exist at replay time. Legacy handlers are checked before claiming an undo\n * log or beginning redo processing. Atomic handlers are checked inside their\n * replay transaction before the guarded source transition; their mutation\n * path can repeat the check after taking domain locks. Throwing aborts replay\n * without committed domain side effects.\n */\n authorizeReplay?(params: CommandReplayAuthorizationArgs<TInput>): Promise<void> | void\n undo?(params: { input: TInput; ctx: CommandRuntimeContext; logEntry: CommandUndoLogEntry }): Promise<void> | void\n /**\n * Optional redo handler. When defined, the command bus calls this instead of\n * `execute()` while replaying a previously undone action (the redo route passes\n * `redoLogEntry` in the execution options). It receives the source action log so\n * it can re-materialize the original record **reusing its id** \u2014 for a create\n * command this restores the soft-deleted row (or re-creates it from the\n * `snapshotAfter`) instead of minting a new id, keeping undo/redo snapshots and\n * references stable (issue #2506, invariant I6). Handlers without `redo` keep the\n * legacy behavior of replaying `execute(__redoInput)`.\n */\n redo?(params: { input: TInput; ctx: CommandRuntimeContext; logEntry: CommandUndoLogEntry }): Promise<TResult> | TResult\n}\n\nexport type CommandExecutionOptions<TInput> = {\n input: TInput\n ctx: CommandRuntimeContext\n metadata?: CommandLogMetadata | null\n skipCacheInvalidation?: boolean\n /**\n * When set, marks this execution as a redo of a previously undone action. If the\n * resolved handler defines a `redo` method, the command bus calls\n * `handler.redo({ input, ctx, logEntry })` instead of `handler.execute(...)`. The\n * rest of the pipeline (snapshots, buildLog, undo-token minting, persistence,\n * cache invalidation, side effects) is identical, so the fresh log entry \u2014 and\n * the `x-om-operation` header derived from it \u2014 automatically carry the restored\n * resourceId. Ignored when the handler has no `redo` method (legacy replay path).\n */\n redoLogEntry?: CommandUndoLogEntry | null\n}\n\nexport function defaultUndoToken(): string {\n return randomUUID()\n}\n"],
5
- "mappings": "AAGA,SAAS,kBAAkB;AA6RpB,SAAS,mBAA2B;AACzC,SAAO,WAAW;AACpB;",
4
+ "sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { ZodTypeAny } from 'zod'\nimport { randomUUID } from 'crypto'\nimport type { AuthContext } from '../auth/server'\nimport type { OrganizationScope } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { TransactionLifetime } from './transaction-lifetime'\n\n/**\n * Bulk-import / backfill deferral flags. When a command runs under a context that\n * carries this, the command bus and data engine suppress the heavy per-record side\n * effects flagged below so a large backfill can defer them to a single batched pass.\n *\n * IMPORTANT \u2014 the caller owns restoring whatever it suppresses. With `skipReindex`\n * the `query_index` projection (and its search tokens) is stale for every record\n * written under this context until the caller runs a batched `query_index rebuild`\n * for the affected entity types at end-of-run.\n *\n * Concurrency: these flags are read from the context and threaded as a local\n * parameter through the side-effect flush \u2014 no shared engine state is mutated \u2014 so\n * two commands running concurrently with different flags never clobber each other.\n */\nexport type BulkImportSuppression = {\n /** Skip the inline `query_index.upsert_one` / `delete_one` reindex (rebuild after the run). */\n skipReindex?: boolean\n /** Skip the per-record `<module>.<entity>.<action>` domain event emission. */\n skipEvents?: boolean\n /** Advisory: handlers that fan out per-record notifications SHOULD honor this and skip them. */\n skipNotifications?: boolean\n /**\n * Skip a command's own full-collection derived-data rebuild (e.g. category tree\n * `parentId`/`treePath`/`depth`/ancestor-descendant rewrite). The caller MUST run that rebuild\n * itself exactly once after the batch, in a `finally` that covers the completed, cancelled and\n * failed paths \u2014 otherwise every record written under this context is left with a stale\n * derived shape.\n */\n skipDerivedRebuild?: boolean\n}\n\nexport type CommandRuntimeContext = {\n container: AwilixContainer\n auth: AuthContext | null\n organizationScope: OrganizationScope | null\n selectedOrganizationId: string | null\n organizationIds: string[] | null\n request?: Request\n syncOrigin?: string | null\n /**\n * See {@link BulkImportSuppression}. Set by bulk backfill callers to defer heavy\n * per-record side effects (reindex, events, notifications). The caller MUST rebuild\n * the `query_index` for the affected entity types after the run when `skipReindex`\n * is set. Unset for normal (interactive) writes \u2014 they get all side effects.\n */\n bulkImport?: BulkImportSuppression\n /**\n * Marks a trusted server-side invocation (CLI seeding, tenant setup) that runs\n * without an authenticated end-user actor. Commands that gate writes behind a\n * privileged actor (e.g. super-admin-only platform tables) may treat this as\n * an explicit system grant. HTTP request paths MUST NOT set this \u2014 they always\n * carry a real `auth` actor, so a present-but-unprivileged actor stays denied.\n */\n systemActor?: boolean\n /**\n * When set, command handlers that support it MUST run their writes within this\n * existing transactional EntityManager (reusing its row locks) instead of\n * opening their own transaction. Lets a caller compose a command with its own\n * surrounding work as a single atomic, single-locked operation.\n */\n transactionalEm?: EntityManager\n /**\n * Optional server-side guard that supported command handlers invoke after\n * opening their write transaction and before reading or mutating records.\n * The guard receives that same transaction-bound EntityManager and, when the\n * handler provides it, the final input after command interceptors. The input\n * stays optional so existing handlers and callers remain source-compatible.\n */\n beforeTransactionalWrite?: (em: EntityManager, input?: unknown) => Promise<void>\n /**\n * Identifies the owner of an already-active {@link transactionalEm} lifetime.\n * Atomic replay accepts an ambient EntityManager only when this is the exact\n * token returned by `getTransactionLifetime(transactionalEm)` from the outer\n * `withAtomicFlush` phase. The owner completes the token after its real commit\n * or rollback, allowing replay leases and post-commit work to follow that\n * boundary. Omit when the command bus opens and owns the transaction itself.\n */\n transactionLifetime?: TransactionLifetime\n /**\n * Optional request-level replay authorization that the command bus binds to\n * its replay EntityManager. Atomic handlers run this guard in the same\n * transaction as the source-log transition and domain mutation; legacy\n * handlers run it in a dedicated read transaction before preserving their\n * existing replay lifecycle.\n */\n replayTransactionGuard?: (args: {\n operation: CommandReplayOperation\n logEntry: CommandUndoLogEntry\n transactionalEm: EntityManager\n }) => Promise<void> | void\n /**\n * On-behalf-of attribution for non-human principals (Agent Identity &\n * On-Behalf-Of, Wave 4 P2). When an agent runs on behalf of a human, the\n * orchestrator's `runAs` wrapper sets this so every `ActionLog` the command\n * path writes records `actorUserId = runAs.actorUserId` (the agent principal's\n * `auth.User` id), `onBehalfOfUserId = runAs.onBehalfOfUserId` (the invoking\n * human, or null for system-invoked agents), and `sourceKey = runAs.source`\n * (`'agent'`). Additive + optional: callers that omit it keep the existing\n * `ctx.auth.sub`-derived attribution unchanged. This threads agent attribution\n * through the SAME audited Command/CRUD path as a human action \u2014 not a parallel\n * audit path.\n */\n runAs?: CommandRunAsContext\n}\n\nexport type CommandRunAsContext = {\n /** The actor stamped on every ActionLog this context produces (agent `auth.User` id). */\n actorUserId: string\n /** The human (or system) principal the actor acts on behalf of; null when system-invoked. */\n onBehalfOfUserId?: string | null\n /** The audit source key for the attributed writes; `'agent'` for agent runs. */\n source: 'agent'\n}\n\nexport type CommandLogMetadata = {\n skipLog?: boolean\n /**\n * Per-execution replay policy. `false` records the audit entry without an undo\n * token or command payload, so neither undo nor redo can replay sensitive input.\n * Omitted preserves the command handler's existing replay behavior.\n */\n replayable?: boolean\n /**\n * Overrides the command input persisted in the audit entry's redo envelope.\n * The default is the raw input, which for credential-bearing commands would put\n * a plaintext secret at rest in `action_logs`. A handler that still wants its\n * operation to be undoable supplies a redacted projection here instead of\n * switching the whole entry to `replayable: false` \u2014 suppressing the undo token\n * removes the operator's ability to revert a write that carries no secret of its\n * own (undoing a user create only deletes a row).\n */\n redoInput?: unknown\n tenantId?: string | null\n organizationId?: string | null\n actorUserId?: string | null\n onBehalfOfUserId?: string | null\n actionLabel?: string | null\n resourceKind?: string | null\n resourceId?: string | null\n parentResourceKind?: string | null\n parentResourceId?: string | null\n undoToken?: string | null\n payload?: unknown\n snapshotBefore?: unknown\n snapshotAfter?: unknown\n relatedResourceKind?: string | null\n relatedResourceId?: string | null\n changes?: Record<string, unknown> | null\n context?: Record<string, unknown> | null\n}\n\nexport type CommandExecuteResult<TResult> = {\n result: TResult\n logEntry: any | null\n /**\n * True when an atomic redo finalized its source action log in the same\n * transaction as the domain mutation and the newly persisted log entry, and\n * that transaction has committed. An ambient replay returns this as false\n * while its caller-owned transaction is still active; the returned result\n * object is updated to true by the owner's commit callback. It remains false\n * on rollback. Callers that historically finalized redo themselves can use\n * this additive signal to avoid a redundant post-commit write.\n */\n replaySourceFinalized?: boolean\n}\n\n/**\n * Shape of the persisted action log handed to a command's `undo()` handler.\n *\n * IMPORTANT: there is intentionally **no `payload` field**. `buildLog()` returns\n * a `payload` in its metadata, but the command bus persists that under\n * `commandPayload` (column `command_payload`, wrapped in a redo envelope) \u2014 the\n * stored row never has a top-level `payload`. Reading `logEntry.payload` in an\n * undo handler is therefore always `undefined` and silently no-ops the undo\n * (issue #2504). Always read the undo snapshot through\n * `extractUndoPayload(logEntry)` from `@open-mercato/shared/lib/commands/undo`,\n * which unwraps `commandPayload`/snapshots correctly. Omitting `payload` here\n * makes the footgun a compile-time error instead of a runtime silent failure.\n */\nexport type CommandUndoLogEntry = {\n id?: string\n commandId?: string\n commandPayload?: unknown | null\n snapshotBefore?: unknown | null\n snapshotAfter?: unknown | null\n resourceKind?: string | null\n resourceId?: string | null\n undoToken?: string | null\n actionLabel?: string | null\n tenantId?: string | null\n organizationId?: string | null\n actorUserId?: string | null\n changesJson?: Record<string, unknown> | null\n contextJson?: Record<string, unknown> | null\n createdAt?: Date | string\n updatedAt?: Date | string\n}\n\nexport type CommandLogBuilderArgs<TInput, TResult> = {\n input: TInput\n result: TResult\n ctx: CommandRuntimeContext\n snapshots: {\n before?: unknown\n after?: unknown\n }\n}\n\nexport type CommandReplayOperation = 'undo' | 'redo'\n\nexport type CommandReplayAuthorizationArgs<TInput> = {\n operation: CommandReplayOperation\n input: TInput\n ctx: CommandRuntimeContext\n logEntry: CommandUndoLogEntry\n}\n\nexport interface CommandHandler<TInput = unknown, TResult = unknown> {\n readonly id: string\n readonly isUndoable?: boolean\n /**\n * Opts replay into the command bus's transaction-bound lifecycle. The source\n * action-log transition, handler mutation, and any new replay log share one\n * EntityManager and commit or roll back together. Handlers that enable this\n * MUST reuse `ctx.transactionalEm` for every replay-time database operation.\n * Omitted preserves the legacy non-atomic replay path.\n */\n readonly atomicReplay?: boolean\n /**\n * Acquires every authorization-state lock needed by an atomic replay before\n * request-level replay authorization and feature-gated interceptors run.\n * Implementations MUST discover their complete actor/target/destination set\n * before taking the first lock and MUST reuse `ctx.transactionalEm`.\n * Omitted preserves the existing lifecycle for handlers that do not need\n * domain-specific authorization stabilization.\n */\n stabilizeReplay?(params: CommandReplayAuthorizationArgs<TInput>): Promise<void> | void\n /**\n * Optional Zod schema describing the command's return value. Feeds the\n * workflows context ledger so downstream activities can reason about the\n * shape a command produces; when absent the ledger renders the output as\n * unknown.\n */\n readonly outputSchema?: ZodTypeAny\n prepare?(input: TInput, ctx: CommandRuntimeContext): Promise<{ before?: unknown } | null> | { before?: unknown } | null\n /**\n * Opt in to taking the undo snapshot inside the write transaction. The bus\n * defers `prepare()` into `ctx.beforeTransactionalWrite`, after the handler's\n * transaction guard and before its first read or mutation.\n */\n readonly prepareSnapshotInsideTransaction?: boolean\n execute(input: TInput, ctx: CommandRuntimeContext): Promise<TResult> | TResult\n buildLog?(args: CommandLogBuilderArgs<TInput, TResult>): Promise<CommandLogMetadata | null | undefined> | CommandLogMetadata | null | undefined\n captureAfter?(input: TInput, result: TResult, ctx: CommandRuntimeContext): Promise<unknown> | unknown\n /**\n * Re-authorizes a stored command against the actor and resource state that\n * exist at replay time. Legacy handlers are checked before claiming an undo\n * log or beginning redo processing. Atomic handlers are checked inside their\n * replay transaction before the guarded source transition; their mutation\n * path can repeat the check after taking domain locks. Throwing aborts replay\n * without committed domain side effects.\n */\n authorizeReplay?(params: CommandReplayAuthorizationArgs<TInput>): Promise<void> | void\n undo?(params: { input: TInput; ctx: CommandRuntimeContext; logEntry: CommandUndoLogEntry }): Promise<void> | void\n /**\n * Optional redo handler. When defined, the command bus calls this instead of\n * `execute()` while replaying a previously undone action (the redo route passes\n * `redoLogEntry` in the execution options). It receives the source action log so\n * it can re-materialize the original record **reusing its id** \u2014 for a create\n * command this restores the soft-deleted row (or re-creates it from the\n * `snapshotAfter`) instead of minting a new id, keeping undo/redo snapshots and\n * references stable (issue #2506, invariant I6). Handlers without `redo` keep the\n * legacy behavior of replaying `execute(__redoInput)`.\n */\n redo?(params: { input: TInput; ctx: CommandRuntimeContext; logEntry: CommandUndoLogEntry }): Promise<TResult> | TResult\n}\n\nexport type CommandExecutionOptions<TInput> = {\n input: TInput\n ctx: CommandRuntimeContext\n metadata?: CommandLogMetadata | null\n skipCacheInvalidation?: boolean\n /**\n * When set, marks this execution as a redo of a previously undone action. If the\n * resolved handler defines a `redo` method, the command bus calls\n * `handler.redo({ input, ctx, logEntry })` instead of `handler.execute(...)`. The\n * rest of the pipeline (snapshots, buildLog, undo-token minting, persistence,\n * cache invalidation, side effects) is identical, so the fresh log entry \u2014 and\n * the `x-om-operation` header derived from it \u2014 automatically carry the restored\n * resourceId. Ignored when the handler has no `redo` method (legacy replay path).\n */\n redoLogEntry?: CommandUndoLogEntry | null\n}\n\nexport function defaultUndoToken(): string {\n return randomUUID()\n}\n"],
5
+ "mappings": "AAGA,SAAS,kBAAkB;AA2SpB,SAAS,mBAA2B;AACzC,SAAO,WAAW;AACpB;",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,4 @@
1
- const APP_VERSION = "0.8.1-develop.7452.1.5e211edd51";
1
+ const APP_VERSION = "0.8.1-develop.7455.1.8f7e53badb";
2
2
  const appVersion = APP_VERSION;
3
3
  export {
4
4
  APP_VERSION,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/version.ts"],
4
- "sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.8.1-develop.7452.1.5e211edd51';\nexport const appVersion = APP_VERSION;\n"],
4
+ "sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.8.1-develop.7455.1.8f7e53badb';\nexport const appVersion = APP_VERSION;\n"],
5
5
  "mappings": "AACO,MAAM,cAAc;AACpB,MAAM,aAAa;",
6
6
  "names": []
7
7
  }
@@ -84,11 +84,22 @@ function notifyInjectionRegistryChanged() {
84
84
  detail: { version: _injectionRegistryVersion }
85
85
  }));
86
86
  }
87
- function registerCoreInjectionWidgets(entries) {
88
- if (_coreInjectionWidgetEntries !== null && process.env.NODE_ENV === "development") {
87
+ function registerCoreInjectionWidgets(entries, options = {}) {
88
+ if (_coreInjectionWidgetEntries !== null && process.env.NODE_ENV === "development" && options.mode !== "merge") {
89
89
  logger.debug("Core injection widgets re-registered (this may occur during HMR)");
90
90
  }
91
- const finalEntries = applyInjectionWidgetOverridesToEntries(entries);
91
+ const incoming = applyInjectionWidgetOverridesToEntries(entries);
92
+ if (options.mode !== "merge") {
93
+ _coreInjectionWidgetEntries = incoming;
94
+ writeGlobalInjectionWidgets(incoming);
95
+ notifyInjectionRegistryChanged();
96
+ return;
97
+ }
98
+ const existing = readGlobalInjectionWidgets() ?? _coreInjectionWidgetEntries ?? [];
99
+ const known = new Set(existing.map((entry) => entry.key));
100
+ const added = incoming.filter((entry) => !known.has(entry.key));
101
+ if (!added.length) return;
102
+ const finalEntries = [...existing, ...added];
92
103
  _coreInjectionWidgetEntries = finalEntries;
93
104
  writeGlobalInjectionWidgets(finalEntries);
94
105
  notifyInjectionRegistryChanged();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/widgets/injection-loader.ts"],
4
- "sourcesContent": ["import type { ModuleInjectionWidgetEntry } from '../registry'\nimport { matchWildcardPattern } from '@open-mercato/shared/lib/patterns/wildcard'\nimport type {\n InjectionAnyWidgetModule,\n InjectionDataWidgetModule,\n InjectionWidgetMetadata,\n InjectionWidgetModule,\n InjectionSpotId,\n ModuleInjectionSlot,\n ModuleInjectionTable,\n InjectionWidgetPlacement,\n} from './injection'\nimport { createLogger } from '../../lib/logger'\nimport {\n applyInjectionWidgetOverridesToEntries,\n applyInjectionWidgetOverridesToTables,\n} from '../overrides'\n\nconst logger = createLogger('widgets').child({ component: 'injection-loader' })\n\ntype LoadedWidgetModule = InjectionWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }\ntype LoadedDataWidgetModule = InjectionDataWidgetModule & { metadata: InjectionWidgetMetadata }\n\nexport type LoadedInjectionWidget = LoadedWidgetModule & {\n moduleId: string\n key: string\n placement?: {\n groupId?: string\n groupLabel?: string\n groupDescription?: string\n column?: 1 | 2\n kind?: 'tab' | 'group' | 'stack'\n [k: string]: unknown\n }\n}\n\nexport type LoadedInjectionDataWidget = LoadedDataWidgetModule & {\n moduleId: string\n key: string\n placement?: {\n groupId?: string\n groupLabel?: string\n groupDescription?: string\n column?: 1 | 2\n kind?: 'tab' | 'group' | 'stack'\n [k: string]: unknown\n }\n}\n\ntype WidgetEntry = ModuleInjectionWidgetEntry & { moduleId: string }\n\n// Registration pattern for publishable packages\nlet _coreInjectionWidgetEntries: ModuleInjectionWidgetEntry[] | null = null\nlet _coreInjectionTables: Array<{ moduleId: string; table: ModuleInjectionTable }> | null = null\nlet _enabledModuleIds: ReadonlySet<string> | null = null\nlet _injectionRegistryVersion = 0\nconst GLOBAL_INJECTION_WIDGETS_KEY = '__openMercatoCoreInjectionWidgetEntries__'\nconst GLOBAL_INJECTION_TABLES_KEY = '__openMercatoCoreInjectionTables__'\nconst GLOBAL_ENABLED_MODULE_IDS_KEY = '__openMercatoEnabledModuleIds__'\nconst GLOBAL_INJECTION_REGISTRY_VERSION_KEY = '__openMercatoCoreInjectionRegistryVersion__'\nconst INJECTION_REGISTRY_CHANGED_EVENT = '__openMercatoInjectionRegistryChanged__'\n\nfunction readGlobalInjectionWidgets(): ModuleInjectionWidgetEntry[] | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_INJECTION_WIDGETS_KEY]\n return Array.isArray(value) ? (value as ModuleInjectionWidgetEntry[]) : null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalInjectionWidgets(entries: ModuleInjectionWidgetEntry[]) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_INJECTION_WIDGETS_KEY] = entries\n } catch {\n // ignore global assignment failures\n }\n}\n\nfunction readGlobalEnabledModuleIds(): ReadonlySet<string> | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_ENABLED_MODULE_IDS_KEY]\n if (value instanceof Set) return value as ReadonlySet<string>\n return null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalEnabledModuleIds(ids: ReadonlySet<string>) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_ENABLED_MODULE_IDS_KEY] = ids\n } catch {\n // ignore global assignment failures\n }\n}\n\nfunction readGlobalInjectionTables(): Array<{ moduleId: string; table: ModuleInjectionTable }> | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_INJECTION_TABLES_KEY]\n return Array.isArray(value) ? (value as Array<{ moduleId: string; table: ModuleInjectionTable }>) : null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalInjectionTables(tables: Array<{ moduleId: string; table: ModuleInjectionTable }>) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_INJECTION_TABLES_KEY] = tables\n } catch {\n // ignore global assignment failures\n }\n}\n\nfunction readGlobalInjectionRegistryVersion(): number | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_INJECTION_REGISTRY_VERSION_KEY]\n return typeof value === 'number' ? value : null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalInjectionRegistryVersion(version: number) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_INJECTION_REGISTRY_VERSION_KEY] = version\n } catch {\n // ignore global assignment failures\n }\n}\n\nfunction notifyInjectionRegistryChanged() {\n _injectionRegistryVersion += 1\n writeGlobalInjectionRegistryVersion(_injectionRegistryVersion)\n invalidateInjectionWidgetCache()\n\n if (typeof window === 'undefined') return\n\n window.dispatchEvent(new CustomEvent(INJECTION_REGISTRY_CHANGED_EVENT, {\n detail: { version: _injectionRegistryVersion },\n }))\n}\n\nexport function registerCoreInjectionWidgets(entries: ModuleInjectionWidgetEntry[]) {\n if (_coreInjectionWidgetEntries !== null && process.env.NODE_ENV === 'development') {\n logger.debug('Core injection widgets re-registered (this may occur during HMR)')\n }\n const finalEntries = applyInjectionWidgetOverridesToEntries(entries)\n _coreInjectionWidgetEntries = finalEntries\n writeGlobalInjectionWidgets(finalEntries)\n notifyInjectionRegistryChanged()\n}\n\nexport function getCoreInjectionWidgets(): ModuleInjectionWidgetEntry[] {\n const globalEntries = readGlobalInjectionWidgets()\n if (globalEntries) return globalEntries\n if (!_coreInjectionWidgetEntries) {\n // On client-side, bootstrap doesn't run - return empty array gracefully\n if (typeof window !== 'undefined') {\n return []\n }\n throw new Error('[Bootstrap] Core injection widgets not registered. Call registerCoreInjectionWidgets() at bootstrap.')\n }\n return _coreInjectionWidgetEntries\n}\n\n/**\n * `widgetEntries` is optional and carries the *unfiltered* generated entries so a\n * `key`-spelled injection-widget override can be resolved to the `widgetId` the table\n * slots reference (#5152). Bootstraps that skip core widget registration have no other\n * source for that mapping, since the registered entries are already override-filtered.\n */\nexport function registerCoreInjectionTables(\n tables: Array<{ moduleId: string; table: ModuleInjectionTable }>,\n widgetEntries?: readonly ModuleInjectionWidgetEntry[],\n) {\n if (_coreInjectionTables !== null && process.env.NODE_ENV === 'development') {\n logger.debug('Core injection tables re-registered (this may occur during HMR)')\n }\n const finalTables = applyInjectionWidgetOverridesToTables(tables, undefined, widgetEntries)\n _coreInjectionTables = finalTables\n writeGlobalInjectionTables(finalTables)\n notifyInjectionRegistryChanged()\n}\n\n/**\n * Register the canonical set of enabled module IDs for the running app.\n *\n * This is the authoritative signal used by `requiredModules` widget gating \u2014\n * deriving \"enabled\" from injection tables or widget entries is unreliable\n * because modules without injection widgets (for example `ai_assistant`) do\n * not contribute entries to either source. Bootstrap callers should pass\n * every module ID present in the app's module registry.\n */\nexport function registerEnabledModuleIds(moduleIds: Iterable<string>) {\n const next = new Set<string>()\n for (const moduleId of moduleIds) {\n if (typeof moduleId === 'string' && moduleId.length > 0) next.add(moduleId)\n }\n if (_enabledModuleIds !== null && process.env.NODE_ENV === 'development') {\n logger.debug('Enabled module IDs re-registered (this may occur during HMR)')\n }\n _enabledModuleIds = next\n writeGlobalEnabledModuleIds(next)\n notifyInjectionRegistryChanged()\n}\n\nexport function getEnabledModuleIds(): ReadonlySet<string> | null {\n return readGlobalEnabledModuleIds() ?? _enabledModuleIds\n}\n\nexport function getInjectionRegistryVersion(): number {\n const globalVersion = readGlobalInjectionRegistryVersion()\n if (globalVersion !== null) return globalVersion\n return _injectionRegistryVersion\n}\n\nconst injectionRegistryChangeSubscribers = new Set<() => void>()\nlet injectionRegistryDomListenerAttached = false\n\nfunction dispatchInjectionRegistryChangeToSubscribers() {\n // Snapshot so a subscriber that unsubscribes during fan-out does not skip others.\n for (const subscriber of Array.from(injectionRegistryChangeSubscribers)) {\n subscriber()\n }\n}\n\n/**\n * Subscribe to injection-registry version changes.\n *\n * All subscribers share a single browser-level DOM listener: the first\n * subscriber attaches the `window` listener and the last one to unsubscribe\n * detaches it. Registry-change notifications fan out to every subscriber\n * through an internal callback set so mounting many widget surfaces does not\n * register one DOM listener per surface (#3320).\n */\nexport function subscribeToInjectionRegistryChanges(listener: () => void): () => void {\n if (typeof window === 'undefined') {\n return () => {}\n }\n\n injectionRegistryChangeSubscribers.add(listener)\n if (!injectionRegistryDomListenerAttached) {\n window.addEventListener(INJECTION_REGISTRY_CHANGED_EVENT, dispatchInjectionRegistryChangeToSubscribers)\n injectionRegistryDomListenerAttached = true\n }\n\n return () => {\n injectionRegistryChangeSubscribers.delete(listener)\n if (injectionRegistryChangeSubscribers.size === 0 && injectionRegistryDomListenerAttached) {\n window.removeEventListener(INJECTION_REGISTRY_CHANGED_EVENT, dispatchInjectionRegistryChangeToSubscribers)\n injectionRegistryDomListenerAttached = false\n }\n }\n}\n\nexport function getCoreInjectionTables(): Array<{ moduleId: string; table: ModuleInjectionTable }> {\n const globalTables = readGlobalInjectionTables()\n if (globalTables) return globalTables\n if (!_coreInjectionTables) {\n // On client-side, bootstrap doesn't run - return empty array gracefully\n if (typeof window !== 'undefined') {\n return []\n }\n throw new Error('[Bootstrap] Core injection tables not registered. Call registerCoreInjectionTables() at bootstrap.')\n }\n return _coreInjectionTables\n}\n\nlet widgetEntriesPromise: Promise<WidgetEntry[]> | null = null\ntype TableEntry = {\n widgetId: string\n moduleId: string\n priority: number\n placement?: ModuleInjectionSlot extends infer S\n ? S extends { widgetId: string }\n ? Omit<S, 'widgetId' | 'priority'>\n : never\n : never\n}\nlet injectionTablePromise: Promise<Map<InjectionSpotId, TableEntry[]>> | null = null\ntype WidgetLookupIndex = {\n widgetsById: Map<string, LoadedInjectionWidget>\n dataWidgetsById: Map<string, LoadedInjectionDataWidget>\n}\nlet widgetLookupIndexPromise: { version: number; promise: Promise<WidgetLookupIndex> } | null = null\n\nfunction isInjectionSlotObject(value: ModuleInjectionSlot): value is InjectionWidgetPlacement & { widgetId: string; priority?: number } {\n return typeof value === 'object' && value !== null && 'widgetId' in value\n}\n\n/**\n * Invalidate the widget entries and widget module cache.\n * Call this when the generated registry is updated or modules are reloaded.\n */\nexport function invalidateInjectionWidgetCache() {\n widgetEntriesPromise = null\n injectionTablePromise = null\n widgetLookupIndexPromise = null\n widgetCache.clear()\n warnedRequiredModuleSkips.clear()\n}\n\nasync function loadWidgetEntries(): Promise<WidgetEntry[]> {\n if (!widgetEntriesPromise) {\n const promise = Promise.resolve().then(() =>\n getCoreInjectionWidgets().map((entry) => ({\n ...entry,\n moduleId: entry.moduleId || 'unknown',\n }))\n )\n widgetEntriesPromise = promise.catch((err) => {\n if (widgetEntriesPromise === promise) {\n widgetEntriesPromise = null\n }\n throw err\n })\n }\n return widgetEntriesPromise\n}\n\nasync function loadInjectionTable(): Promise<Map<InjectionSpotId, TableEntry[]>> {\n if (!injectionTablePromise) {\n const promise = Promise.resolve().then(() => {\n const list = getCoreInjectionTables()\n const table = new Map<InjectionSpotId, TableEntry[]>()\n\n for (const entry of list) {\n const injectionTable = entry.table ?? {}\n for (const [spotId, widgetIds] of Object.entries(injectionTable)) {\n const widgets = Array.isArray(widgetIds) ? widgetIds : [widgetIds]\n const existing = table.get(spotId) ?? []\n for (const widgetEntry of widgets) {\n if (typeof widgetEntry === 'string') {\n existing.push({ widgetId: widgetEntry, moduleId: entry.moduleId, priority: 0 })\n continue\n }\n if (isInjectionSlotObject(widgetEntry)) {\n const { widgetId, priority = 0, ...placement } = widgetEntry\n existing.push({\n widgetId,\n moduleId: entry.moduleId,\n priority: typeof priority === 'number' ? priority : 0,\n placement,\n })\n continue\n }\n }\n table.set(spotId, existing)\n }\n }\n\n for (const [spotId, widgets] of table.entries()) {\n table.set(spotId, widgets.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)))\n }\n\n return table\n })\n injectionTablePromise = promise.catch((err) => {\n if (injectionTablePromise === promise) {\n injectionTablePromise = null\n }\n throw err\n })\n }\n return injectionTablePromise\n}\n\nconst widgetCache = new Map<string, Promise<InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }>>()\n\nfunction isDataWidgetModule(widget: Record<string, unknown>): widget is LoadedDataWidgetModule {\n const keys = [\n 'columns',\n 'rowActions',\n 'bulkActions',\n 'filters',\n 'fields',\n 'steps',\n 'badge',\n 'menuItems',\n ]\n return keys.some((key) => key in widget)\n}\n\nfunction ensureValidInjectionModule(mod: unknown, key: string, moduleId: string): (InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }) {\n if (!mod || typeof mod !== 'object') {\n throw new Error(`Invalid injection widget module \"${key}\" from \"${moduleId}\" (expected object export)`)\n }\n const widget = (mod as { default?: InjectionAnyWidgetModule<any, any> }).default ?? (mod as InjectionAnyWidgetModule<any, any>)\n if (!widget || typeof widget !== 'object') {\n throw new Error(`Invalid injection widget export \"${key}\" from \"${moduleId}\" (missing default export)`) \n }\n if (!('metadata' in widget) || !widget.metadata || typeof widget.metadata !== 'object') {\n throw new Error(`Injection widget \"${key}\" from \"${moduleId}\" is missing metadata`)\n }\n const metadata = widget.metadata\n if (typeof metadata.id !== 'string' || metadata.id.length === 0) {\n throw new Error(`Injection widget \"${key}\" from \"${moduleId}\" metadata.id must be a non-empty string`)\n }\n const normalized = {\n ...widget,\n metadata,\n }\n\n if ('Widget' in normalized && typeof normalized.Widget === 'function') {\n if (typeof metadata.title !== 'string' || metadata.title.length === 0) {\n throw new Error(`Injection widget \"${metadata.id}\" from \"${moduleId}\" must have a title`)\n }\n return normalized\n }\n\n if (!isDataWidgetModule(normalized as Record<string, unknown>)) {\n throw new Error(\n `Injection widget \"${metadata.id}\" from \"${moduleId}\" must export either Widget component or a declarative data payload`\n )\n }\n\n return normalized\n}\n\nfunction isLoadedInjectionWidget(\n module: InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }\n): module is LoadedWidgetModule {\n return 'Widget' in module && typeof module.Widget === 'function'\n}\n\nfunction isLoadedInjectionDataWidget(\n module: InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }\n): module is LoadedDataWidgetModule {\n return !isLoadedInjectionWidget(module)\n}\n\nasync function loadEntry(entry: WidgetEntry): Promise<InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }> {\n if (!widgetCache.has(entry.key)) {\n const promise = Promise.resolve()\n .then(() => entry.loader())\n .then((mod) => ensureValidInjectionModule(mod, entry.key, entry.moduleId))\n widgetCache.set(entry.key, promise)\n }\n return widgetCache.get(entry.key)!\n}\n\nasync function loadWidgetLookupIndex(): Promise<WidgetLookupIndex> {\n const version = getInjectionRegistryVersion()\n if (!widgetLookupIndexPromise || widgetLookupIndexPromise.version !== version) {\n const promise = Promise.resolve().then(async () => {\n const widgetEntries = await loadWidgetEntries()\n const settled = await Promise.allSettled(widgetEntries.map((entry) => loadEntry(entry)))\n const widgetsById = new Map<string, LoadedInjectionWidget>()\n const dataWidgetsById = new Map<string, LoadedInjectionDataWidget>()\n\n settled.forEach((result, index) => {\n if (result.status !== 'fulfilled') return\n const entry = widgetEntries[index]\n const module = result.value\n if (isLoadedInjectionWidget(module)) {\n if (!widgetsById.has(module.metadata.id)) {\n widgetsById.set(module.metadata.id, { ...module, moduleId: entry.moduleId, key: entry.key })\n }\n return\n }\n if (!dataWidgetsById.has(module.metadata.id)) {\n dataWidgetsById.set(module.metadata.id, { ...module, moduleId: entry.moduleId, key: entry.key })\n }\n })\n\n return { widgetsById, dataWidgetsById }\n })\n widgetLookupIndexPromise = { version, promise }\n }\n return widgetLookupIndexPromise.promise\n}\n\nfunction applyRequiredModuleGate<T extends LoadedInjectionWidget | LoadedInjectionDataWidget>(\n widget: T,\n enabledModuleIds: ReadonlySet<string>,\n): T | null {\n const missing = widgetMissingRequiredModules(widget.metadata, enabledModuleIds)\n if (missing.length > 0) {\n warnSkippedWidget(widget.metadata.id, missing)\n return null\n }\n return widget\n}\n\ntype HintedLookupResult<T> =\n | { resolved: true; widget: T | null }\n | { resolved: false }\n\nasync function tryLoadHintedWidgetById<T extends LoadedInjectionWidget | LoadedInjectionDataWidget>(\n widgetId: string,\n isExpectedKind: (module: InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }) => boolean,\n enabledModuleIds: ReadonlySet<string>,\n): Promise<HintedLookupResult<T>> {\n const widgetEntries = await loadWidgetEntries()\n const entry = widgetEntries.find((candidate) => candidate.widgetId === widgetId)\n if (!entry) return { resolved: false }\n\n const module = await loadEntry(entry).catch(() => null)\n if (!module || module.metadata.id !== widgetId || !isExpectedKind(module)) {\n return { resolved: false }\n }\n\n const widget = { ...module, moduleId: entry.moduleId, key: entry.key } as T\n return { resolved: true, widget: applyRequiredModuleGate(widget, enabledModuleIds) }\n}\n\nfunction getEnabledModuleIdsForInjection(): ReadonlySet<string> {\n // Prefer the explicit enabled-modules registry populated by bootstrap.\n // This is the only signal that includes modules without injection widgets\n // (for example `ai_assistant`), so it is required for `requiredModules`\n // gating to be sound.\n const explicit = readGlobalEnabledModuleIds() ?? _enabledModuleIds\n if (explicit) return explicit\n\n // Fallback: derive from injection tables and widget entries. This keeps\n // older bootstrap paths (and callers that have not yet wired\n // `registerEnabledModuleIds`) working \u2014 at the cost of mis-classifying\n // dependency modules that ship no widgets. New apps MUST call\n // `registerEnabledModuleIds` to get accurate gating.\n const enabled = new Set<string>()\n const tables = readGlobalInjectionTables() ?? _coreInjectionTables ?? []\n for (const entry of tables) {\n if (entry?.moduleId) enabled.add(entry.moduleId)\n }\n const entries = readGlobalInjectionWidgets() ?? _coreInjectionWidgetEntries ?? []\n for (const entry of entries) {\n if (entry?.moduleId) enabled.add(entry.moduleId)\n }\n return enabled\n}\n\nfunction widgetMissingRequiredModules(\n metadata: InjectionWidgetMetadata,\n enabledModuleIds: ReadonlySet<string>,\n): string[] {\n const required = metadata.requiredModules\n if (!Array.isArray(required) || required.length === 0) return []\n const missing: string[] = []\n for (const moduleId of required) {\n if (typeof moduleId !== 'string' || moduleId.length === 0) continue\n if (!enabledModuleIds.has(moduleId)) missing.push(moduleId)\n }\n return missing\n}\n\nconst warnedRequiredModuleSkips = new Set<string>()\n\nfunction warnSkippedWidget(metadataId: string, missingModules: string[]) {\n const key = `${metadataId}:${missingModules.join(',')}`\n if (warnedRequiredModuleSkips.has(key)) return\n warnedRequiredModuleSkips.add(key)\n if (process.env.NODE_ENV === 'development') {\n logger.debug('Skipping widget \u2014 required module(s) not enabled', { metadataId, missingModules })\n }\n}\n\nasync function getResolvedEntriesForSpot(spotId: InjectionSpotId): Promise<TableEntry[]> {\n const table = await loadInjectionTable()\n const exactEntries = table.get(spotId) ?? []\n const wildcardEntries: TableEntry[] = []\n\n for (const [candidateSpotId, candidateEntries] of table.entries()) {\n if (candidateSpotId === spotId) continue\n if (!candidateSpotId.includes('*')) continue\n if (!matchWildcardPattern(spotId, candidateSpotId)) continue\n wildcardEntries.push(...candidateEntries)\n }\n\n const dedupedEntries = new Map<string, TableEntry>()\n for (const entry of [...exactEntries, ...wildcardEntries]) {\n const cacheKey = `${entry.moduleId}:${entry.widgetId}`\n const previous = dedupedEntries.get(cacheKey)\n if (!previous || (entry.priority ?? 0) > (previous.priority ?? 0)) {\n dedupedEntries.set(cacheKey, entry)\n }\n }\n\n return Array.from(dedupedEntries.values()).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0))\n}\n\nexport async function loadAllInjectionWidgets(): Promise<LoadedInjectionWidget[]> {\n const widgetEntries = await loadWidgetEntries()\n const enabledModuleIds = getEnabledModuleIdsForInjection()\n const loaded = await Promise.all(\n widgetEntries.map(async (entry) => {\n const module = await loadEntry(entry)\n if (!isLoadedInjectionWidget(module)) return null\n const missing = widgetMissingRequiredModules(module.metadata, enabledModuleIds)\n if (missing.length > 0) {\n warnSkippedWidget(module.metadata.id, missing)\n return null\n }\n return { ...module, moduleId: entry.moduleId, key: entry.key }\n })\n )\n const byId = new Map<string, LoadedInjectionWidget>()\n for (const widget of loaded) {\n if (!widget) continue\n if (!byId.has(widget.metadata.id)) {\n byId.set(widget.metadata.id, widget)\n }\n }\n return Array.from(byId.values())\n}\n\nexport async function loadInjectionWidgetById(widgetId: string): Promise<LoadedInjectionWidget | null> {\n const enabledModuleIds = getEnabledModuleIdsForInjection()\n const hinted = await tryLoadHintedWidgetById<LoadedInjectionWidget>(widgetId, isLoadedInjectionWidget, enabledModuleIds)\n if (hinted.resolved) return hinted.widget\n\n const index = await loadWidgetLookupIndex()\n const widget = index.widgetsById.get(widgetId)\n return widget ? applyRequiredModuleGate(widget, enabledModuleIds) : null\n}\n\nexport async function loadInjectionDataWidgetById(widgetId: string): Promise<LoadedInjectionDataWidget | null> {\n const enabledModuleIds = getEnabledModuleIdsForInjection()\n const hinted = await tryLoadHintedWidgetById<LoadedInjectionDataWidget>(widgetId, isLoadedInjectionDataWidget, enabledModuleIds)\n if (hinted.resolved) return hinted.widget\n\n const index = await loadWidgetLookupIndex()\n const widget = index.dataWidgetsById.get(widgetId)\n return widget ? applyRequiredModuleGate(widget, enabledModuleIds) : null\n}\n\nexport async function loadInjectionWidgetsForSpot(spotId: InjectionSpotId): Promise<LoadedInjectionWidget[]> {\n const entries = await getResolvedEntriesForSpot(spotId)\n const widgets: LoadedInjectionWidget[] = []\n for (const { widgetId, placement, priority } of entries) {\n const widget = await loadInjectionWidgetById(widgetId)\n if (!widget) continue\n const combinedPlacement = placement\n ? { ...placement, priority: typeof priority === 'number' ? priority : 0 }\n : { priority: typeof priority === 'number' ? priority : 0 }\n widgets.push({ ...widget, placement: combinedPlacement })\n }\n return widgets\n}\n\nexport async function loadInjectionDataWidgetsForSpot(spotId: InjectionSpotId): Promise<LoadedInjectionDataWidget[]> {\n const entries = await getResolvedEntriesForSpot(spotId)\n const widgets: LoadedInjectionDataWidget[] = []\n for (const { widgetId, placement, priority } of entries) {\n const widget = await loadInjectionDataWidgetById(widgetId)\n if (!widget) continue\n const combinedPlacement = placement\n ? { ...placement, priority: typeof priority === 'number' ? priority : 0 }\n : { priority: typeof priority === 'number' ? priority : 0 }\n widgets.push({ ...widget, placement: combinedPlacement })\n }\n return widgets\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { ModuleInjectionWidgetEntry } from '../registry'\nimport { matchWildcardPattern } from '@open-mercato/shared/lib/patterns/wildcard'\nimport type {\n InjectionAnyWidgetModule,\n InjectionDataWidgetModule,\n InjectionWidgetMetadata,\n InjectionWidgetModule,\n InjectionSpotId,\n ModuleInjectionSlot,\n ModuleInjectionTable,\n InjectionWidgetPlacement,\n} from './injection'\nimport { createLogger } from '../../lib/logger'\nimport {\n applyInjectionWidgetOverridesToEntries,\n applyInjectionWidgetOverridesToTables,\n} from '../overrides'\n\nconst logger = createLogger('widgets').child({ component: 'injection-loader' })\n\ntype LoadedWidgetModule = InjectionWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }\ntype LoadedDataWidgetModule = InjectionDataWidgetModule & { metadata: InjectionWidgetMetadata }\n\nexport type LoadedInjectionWidget = LoadedWidgetModule & {\n moduleId: string\n key: string\n placement?: {\n groupId?: string\n groupLabel?: string\n groupDescription?: string\n column?: 1 | 2\n kind?: 'tab' | 'group' | 'stack'\n [k: string]: unknown\n }\n}\n\nexport type LoadedInjectionDataWidget = LoadedDataWidgetModule & {\n moduleId: string\n key: string\n placement?: {\n groupId?: string\n groupLabel?: string\n groupDescription?: string\n column?: 1 | 2\n kind?: 'tab' | 'group' | 'stack'\n [k: string]: unknown\n }\n}\n\ntype WidgetEntry = ModuleInjectionWidgetEntry & { moduleId: string }\n\n// Registration pattern for publishable packages\nlet _coreInjectionWidgetEntries: ModuleInjectionWidgetEntry[] | null = null\nlet _coreInjectionTables: Array<{ moduleId: string; table: ModuleInjectionTable }> | null = null\nlet _enabledModuleIds: ReadonlySet<string> | null = null\nlet _injectionRegistryVersion = 0\nconst GLOBAL_INJECTION_WIDGETS_KEY = '__openMercatoCoreInjectionWidgetEntries__'\nconst GLOBAL_INJECTION_TABLES_KEY = '__openMercatoCoreInjectionTables__'\nconst GLOBAL_ENABLED_MODULE_IDS_KEY = '__openMercatoEnabledModuleIds__'\nconst GLOBAL_INJECTION_REGISTRY_VERSION_KEY = '__openMercatoCoreInjectionRegistryVersion__'\nconst INJECTION_REGISTRY_CHANGED_EVENT = '__openMercatoInjectionRegistryChanged__'\n\nfunction readGlobalInjectionWidgets(): ModuleInjectionWidgetEntry[] | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_INJECTION_WIDGETS_KEY]\n return Array.isArray(value) ? (value as ModuleInjectionWidgetEntry[]) : null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalInjectionWidgets(entries: ModuleInjectionWidgetEntry[]) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_INJECTION_WIDGETS_KEY] = entries\n } catch {\n // ignore global assignment failures\n }\n}\n\nfunction readGlobalEnabledModuleIds(): ReadonlySet<string> | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_ENABLED_MODULE_IDS_KEY]\n if (value instanceof Set) return value as ReadonlySet<string>\n return null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalEnabledModuleIds(ids: ReadonlySet<string>) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_ENABLED_MODULE_IDS_KEY] = ids\n } catch {\n // ignore global assignment failures\n }\n}\n\nfunction readGlobalInjectionTables(): Array<{ moduleId: string; table: ModuleInjectionTable }> | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_INJECTION_TABLES_KEY]\n return Array.isArray(value) ? (value as Array<{ moduleId: string; table: ModuleInjectionTable }>) : null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalInjectionTables(tables: Array<{ moduleId: string; table: ModuleInjectionTable }>) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_INJECTION_TABLES_KEY] = tables\n } catch {\n // ignore global assignment failures\n }\n}\n\nfunction readGlobalInjectionRegistryVersion(): number | null {\n try {\n const value = (globalThis as Record<string, unknown>)[GLOBAL_INJECTION_REGISTRY_VERSION_KEY]\n return typeof value === 'number' ? value : null\n } catch {\n return null\n }\n}\n\nfunction writeGlobalInjectionRegistryVersion(version: number) {\n try {\n ;(globalThis as Record<string, unknown>)[GLOBAL_INJECTION_REGISTRY_VERSION_KEY] = version\n } catch {\n // ignore global assignment failures\n }\n}\n\nfunction notifyInjectionRegistryChanged() {\n _injectionRegistryVersion += 1\n writeGlobalInjectionRegistryVersion(_injectionRegistryVersion)\n invalidateInjectionWidgetCache()\n\n if (typeof window === 'undefined') return\n\n window.dispatchEvent(new CustomEvent(INJECTION_REGISTRY_CHANGED_EVENT, {\n detail: { version: _injectionRegistryVersion },\n }))\n}\n\nexport type RegisterCoreInjectionWidgetsOptions = {\n /**\n * `replace` (default) publishes `entries` as the whole registry.\n *\n * `merge` adds only the entries the registry does not already carry, keeping whatever a\n * previous bootstrap published. A process may run more than one bootstrap partition (the\n * API-only one and the full page one share a Node process in a mixed web host), and the\n * partition that registers second must not shrink the registry the first one populated.\n */\n mode?: 'replace' | 'merge'\n}\n\nexport function registerCoreInjectionWidgets(\n entries: ModuleInjectionWidgetEntry[],\n options: RegisterCoreInjectionWidgetsOptions = {},\n) {\n if (_coreInjectionWidgetEntries !== null && process.env.NODE_ENV === 'development' && options.mode !== 'merge') {\n logger.debug('Core injection widgets re-registered (this may occur during HMR)')\n }\n const incoming = applyInjectionWidgetOverridesToEntries(entries)\n if (options.mode !== 'merge') {\n _coreInjectionWidgetEntries = incoming\n writeGlobalInjectionWidgets(incoming)\n notifyInjectionRegistryChanged()\n return\n }\n\n const existing = readGlobalInjectionWidgets() ?? _coreInjectionWidgetEntries ?? []\n const known = new Set(existing.map((entry) => entry.key))\n const added = incoming.filter((entry) => !known.has(entry.key))\n // Nothing to contribute: leave the registry exactly as it was. Publishing an empty array here\n // would turn \"never registered\" into \"registered with no widgets\", which downgrades the loud\n // bootstrap error into silently missing contributions.\n if (!added.length) return\n const finalEntries = [...existing, ...added]\n _coreInjectionWidgetEntries = finalEntries\n writeGlobalInjectionWidgets(finalEntries)\n notifyInjectionRegistryChanged()\n}\n\nexport function getCoreInjectionWidgets(): ModuleInjectionWidgetEntry[] {\n const globalEntries = readGlobalInjectionWidgets()\n if (globalEntries) return globalEntries\n if (!_coreInjectionWidgetEntries) {\n // On client-side, bootstrap doesn't run - return empty array gracefully\n if (typeof window !== 'undefined') {\n return []\n }\n throw new Error('[Bootstrap] Core injection widgets not registered. Call registerCoreInjectionWidgets() at bootstrap.')\n }\n return _coreInjectionWidgetEntries\n}\n\n/**\n * `widgetEntries` is optional and carries the *unfiltered* generated entries so a\n * `key`-spelled injection-widget override can be resolved to the `widgetId` the table\n * slots reference (#5152). Bootstraps that skip core widget registration have no other\n * source for that mapping, since the registered entries are already override-filtered.\n */\nexport function registerCoreInjectionTables(\n tables: Array<{ moduleId: string; table: ModuleInjectionTable }>,\n widgetEntries?: readonly ModuleInjectionWidgetEntry[],\n) {\n if (_coreInjectionTables !== null && process.env.NODE_ENV === 'development') {\n logger.debug('Core injection tables re-registered (this may occur during HMR)')\n }\n const finalTables = applyInjectionWidgetOverridesToTables(tables, undefined, widgetEntries)\n _coreInjectionTables = finalTables\n writeGlobalInjectionTables(finalTables)\n notifyInjectionRegistryChanged()\n}\n\n/**\n * Register the canonical set of enabled module IDs for the running app.\n *\n * This is the authoritative signal used by `requiredModules` widget gating \u2014\n * deriving \"enabled\" from injection tables or widget entries is unreliable\n * because modules without injection widgets (for example `ai_assistant`) do\n * not contribute entries to either source. Bootstrap callers should pass\n * every module ID present in the app's module registry.\n */\nexport function registerEnabledModuleIds(moduleIds: Iterable<string>) {\n const next = new Set<string>()\n for (const moduleId of moduleIds) {\n if (typeof moduleId === 'string' && moduleId.length > 0) next.add(moduleId)\n }\n if (_enabledModuleIds !== null && process.env.NODE_ENV === 'development') {\n logger.debug('Enabled module IDs re-registered (this may occur during HMR)')\n }\n _enabledModuleIds = next\n writeGlobalEnabledModuleIds(next)\n notifyInjectionRegistryChanged()\n}\n\nexport function getEnabledModuleIds(): ReadonlySet<string> | null {\n return readGlobalEnabledModuleIds() ?? _enabledModuleIds\n}\n\nexport function getInjectionRegistryVersion(): number {\n const globalVersion = readGlobalInjectionRegistryVersion()\n if (globalVersion !== null) return globalVersion\n return _injectionRegistryVersion\n}\n\nconst injectionRegistryChangeSubscribers = new Set<() => void>()\nlet injectionRegistryDomListenerAttached = false\n\nfunction dispatchInjectionRegistryChangeToSubscribers() {\n // Snapshot so a subscriber that unsubscribes during fan-out does not skip others.\n for (const subscriber of Array.from(injectionRegistryChangeSubscribers)) {\n subscriber()\n }\n}\n\n/**\n * Subscribe to injection-registry version changes.\n *\n * All subscribers share a single browser-level DOM listener: the first\n * subscriber attaches the `window` listener and the last one to unsubscribe\n * detaches it. Registry-change notifications fan out to every subscriber\n * through an internal callback set so mounting many widget surfaces does not\n * register one DOM listener per surface (#3320).\n */\nexport function subscribeToInjectionRegistryChanges(listener: () => void): () => void {\n if (typeof window === 'undefined') {\n return () => {}\n }\n\n injectionRegistryChangeSubscribers.add(listener)\n if (!injectionRegistryDomListenerAttached) {\n window.addEventListener(INJECTION_REGISTRY_CHANGED_EVENT, dispatchInjectionRegistryChangeToSubscribers)\n injectionRegistryDomListenerAttached = true\n }\n\n return () => {\n injectionRegistryChangeSubscribers.delete(listener)\n if (injectionRegistryChangeSubscribers.size === 0 && injectionRegistryDomListenerAttached) {\n window.removeEventListener(INJECTION_REGISTRY_CHANGED_EVENT, dispatchInjectionRegistryChangeToSubscribers)\n injectionRegistryDomListenerAttached = false\n }\n }\n}\n\nexport function getCoreInjectionTables(): Array<{ moduleId: string; table: ModuleInjectionTable }> {\n const globalTables = readGlobalInjectionTables()\n if (globalTables) return globalTables\n if (!_coreInjectionTables) {\n // On client-side, bootstrap doesn't run - return empty array gracefully\n if (typeof window !== 'undefined') {\n return []\n }\n throw new Error('[Bootstrap] Core injection tables not registered. Call registerCoreInjectionTables() at bootstrap.')\n }\n return _coreInjectionTables\n}\n\nlet widgetEntriesPromise: Promise<WidgetEntry[]> | null = null\ntype TableEntry = {\n widgetId: string\n moduleId: string\n priority: number\n placement?: ModuleInjectionSlot extends infer S\n ? S extends { widgetId: string }\n ? Omit<S, 'widgetId' | 'priority'>\n : never\n : never\n}\nlet injectionTablePromise: Promise<Map<InjectionSpotId, TableEntry[]>> | null = null\ntype WidgetLookupIndex = {\n widgetsById: Map<string, LoadedInjectionWidget>\n dataWidgetsById: Map<string, LoadedInjectionDataWidget>\n}\nlet widgetLookupIndexPromise: { version: number; promise: Promise<WidgetLookupIndex> } | null = null\n\nfunction isInjectionSlotObject(value: ModuleInjectionSlot): value is InjectionWidgetPlacement & { widgetId: string; priority?: number } {\n return typeof value === 'object' && value !== null && 'widgetId' in value\n}\n\n/**\n * Invalidate the widget entries and widget module cache.\n * Call this when the generated registry is updated or modules are reloaded.\n */\nexport function invalidateInjectionWidgetCache() {\n widgetEntriesPromise = null\n injectionTablePromise = null\n widgetLookupIndexPromise = null\n widgetCache.clear()\n warnedRequiredModuleSkips.clear()\n}\n\nasync function loadWidgetEntries(): Promise<WidgetEntry[]> {\n if (!widgetEntriesPromise) {\n const promise = Promise.resolve().then(() =>\n getCoreInjectionWidgets().map((entry) => ({\n ...entry,\n moduleId: entry.moduleId || 'unknown',\n }))\n )\n widgetEntriesPromise = promise.catch((err) => {\n if (widgetEntriesPromise === promise) {\n widgetEntriesPromise = null\n }\n throw err\n })\n }\n return widgetEntriesPromise\n}\n\nasync function loadInjectionTable(): Promise<Map<InjectionSpotId, TableEntry[]>> {\n if (!injectionTablePromise) {\n const promise = Promise.resolve().then(() => {\n const list = getCoreInjectionTables()\n const table = new Map<InjectionSpotId, TableEntry[]>()\n\n for (const entry of list) {\n const injectionTable = entry.table ?? {}\n for (const [spotId, widgetIds] of Object.entries(injectionTable)) {\n const widgets = Array.isArray(widgetIds) ? widgetIds : [widgetIds]\n const existing = table.get(spotId) ?? []\n for (const widgetEntry of widgets) {\n if (typeof widgetEntry === 'string') {\n existing.push({ widgetId: widgetEntry, moduleId: entry.moduleId, priority: 0 })\n continue\n }\n if (isInjectionSlotObject(widgetEntry)) {\n const { widgetId, priority = 0, ...placement } = widgetEntry\n existing.push({\n widgetId,\n moduleId: entry.moduleId,\n priority: typeof priority === 'number' ? priority : 0,\n placement,\n })\n continue\n }\n }\n table.set(spotId, existing)\n }\n }\n\n for (const [spotId, widgets] of table.entries()) {\n table.set(spotId, widgets.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)))\n }\n\n return table\n })\n injectionTablePromise = promise.catch((err) => {\n if (injectionTablePromise === promise) {\n injectionTablePromise = null\n }\n throw err\n })\n }\n return injectionTablePromise\n}\n\nconst widgetCache = new Map<string, Promise<InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }>>()\n\nfunction isDataWidgetModule(widget: Record<string, unknown>): widget is LoadedDataWidgetModule {\n const keys = [\n 'columns',\n 'rowActions',\n 'bulkActions',\n 'filters',\n 'fields',\n 'steps',\n 'badge',\n 'menuItems',\n ]\n return keys.some((key) => key in widget)\n}\n\nfunction ensureValidInjectionModule(mod: unknown, key: string, moduleId: string): (InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }) {\n if (!mod || typeof mod !== 'object') {\n throw new Error(`Invalid injection widget module \"${key}\" from \"${moduleId}\" (expected object export)`)\n }\n const widget = (mod as { default?: InjectionAnyWidgetModule<any, any> }).default ?? (mod as InjectionAnyWidgetModule<any, any>)\n if (!widget || typeof widget !== 'object') {\n throw new Error(`Invalid injection widget export \"${key}\" from \"${moduleId}\" (missing default export)`) \n }\n if (!('metadata' in widget) || !widget.metadata || typeof widget.metadata !== 'object') {\n throw new Error(`Injection widget \"${key}\" from \"${moduleId}\" is missing metadata`)\n }\n const metadata = widget.metadata\n if (typeof metadata.id !== 'string' || metadata.id.length === 0) {\n throw new Error(`Injection widget \"${key}\" from \"${moduleId}\" metadata.id must be a non-empty string`)\n }\n const normalized = {\n ...widget,\n metadata,\n }\n\n if ('Widget' in normalized && typeof normalized.Widget === 'function') {\n if (typeof metadata.title !== 'string' || metadata.title.length === 0) {\n throw new Error(`Injection widget \"${metadata.id}\" from \"${moduleId}\" must have a title`)\n }\n return normalized\n }\n\n if (!isDataWidgetModule(normalized as Record<string, unknown>)) {\n throw new Error(\n `Injection widget \"${metadata.id}\" from \"${moduleId}\" must export either Widget component or a declarative data payload`\n )\n }\n\n return normalized\n}\n\nfunction isLoadedInjectionWidget(\n module: InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }\n): module is LoadedWidgetModule {\n return 'Widget' in module && typeof module.Widget === 'function'\n}\n\nfunction isLoadedInjectionDataWidget(\n module: InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }\n): module is LoadedDataWidgetModule {\n return !isLoadedInjectionWidget(module)\n}\n\nasync function loadEntry(entry: WidgetEntry): Promise<InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }> {\n if (!widgetCache.has(entry.key)) {\n const promise = Promise.resolve()\n .then(() => entry.loader())\n .then((mod) => ensureValidInjectionModule(mod, entry.key, entry.moduleId))\n widgetCache.set(entry.key, promise)\n }\n return widgetCache.get(entry.key)!\n}\n\nasync function loadWidgetLookupIndex(): Promise<WidgetLookupIndex> {\n const version = getInjectionRegistryVersion()\n if (!widgetLookupIndexPromise || widgetLookupIndexPromise.version !== version) {\n const promise = Promise.resolve().then(async () => {\n const widgetEntries = await loadWidgetEntries()\n const settled = await Promise.allSettled(widgetEntries.map((entry) => loadEntry(entry)))\n const widgetsById = new Map<string, LoadedInjectionWidget>()\n const dataWidgetsById = new Map<string, LoadedInjectionDataWidget>()\n\n settled.forEach((result, index) => {\n if (result.status !== 'fulfilled') return\n const entry = widgetEntries[index]\n const module = result.value\n if (isLoadedInjectionWidget(module)) {\n if (!widgetsById.has(module.metadata.id)) {\n widgetsById.set(module.metadata.id, { ...module, moduleId: entry.moduleId, key: entry.key })\n }\n return\n }\n if (!dataWidgetsById.has(module.metadata.id)) {\n dataWidgetsById.set(module.metadata.id, { ...module, moduleId: entry.moduleId, key: entry.key })\n }\n })\n\n return { widgetsById, dataWidgetsById }\n })\n widgetLookupIndexPromise = { version, promise }\n }\n return widgetLookupIndexPromise.promise\n}\n\nfunction applyRequiredModuleGate<T extends LoadedInjectionWidget | LoadedInjectionDataWidget>(\n widget: T,\n enabledModuleIds: ReadonlySet<string>,\n): T | null {\n const missing = widgetMissingRequiredModules(widget.metadata, enabledModuleIds)\n if (missing.length > 0) {\n warnSkippedWidget(widget.metadata.id, missing)\n return null\n }\n return widget\n}\n\ntype HintedLookupResult<T> =\n | { resolved: true; widget: T | null }\n | { resolved: false }\n\nasync function tryLoadHintedWidgetById<T extends LoadedInjectionWidget | LoadedInjectionDataWidget>(\n widgetId: string,\n isExpectedKind: (module: InjectionAnyWidgetModule<any, any> & { metadata: InjectionWidgetMetadata }) => boolean,\n enabledModuleIds: ReadonlySet<string>,\n): Promise<HintedLookupResult<T>> {\n const widgetEntries = await loadWidgetEntries()\n const entry = widgetEntries.find((candidate) => candidate.widgetId === widgetId)\n if (!entry) return { resolved: false }\n\n const module = await loadEntry(entry).catch(() => null)\n if (!module || module.metadata.id !== widgetId || !isExpectedKind(module)) {\n return { resolved: false }\n }\n\n const widget = { ...module, moduleId: entry.moduleId, key: entry.key } as T\n return { resolved: true, widget: applyRequiredModuleGate(widget, enabledModuleIds) }\n}\n\nfunction getEnabledModuleIdsForInjection(): ReadonlySet<string> {\n // Prefer the explicit enabled-modules registry populated by bootstrap.\n // This is the only signal that includes modules without injection widgets\n // (for example `ai_assistant`), so it is required for `requiredModules`\n // gating to be sound.\n const explicit = readGlobalEnabledModuleIds() ?? _enabledModuleIds\n if (explicit) return explicit\n\n // Fallback: derive from injection tables and widget entries. This keeps\n // older bootstrap paths (and callers that have not yet wired\n // `registerEnabledModuleIds`) working \u2014 at the cost of mis-classifying\n // dependency modules that ship no widgets. New apps MUST call\n // `registerEnabledModuleIds` to get accurate gating.\n const enabled = new Set<string>()\n const tables = readGlobalInjectionTables() ?? _coreInjectionTables ?? []\n for (const entry of tables) {\n if (entry?.moduleId) enabled.add(entry.moduleId)\n }\n const entries = readGlobalInjectionWidgets() ?? _coreInjectionWidgetEntries ?? []\n for (const entry of entries) {\n if (entry?.moduleId) enabled.add(entry.moduleId)\n }\n return enabled\n}\n\nfunction widgetMissingRequiredModules(\n metadata: InjectionWidgetMetadata,\n enabledModuleIds: ReadonlySet<string>,\n): string[] {\n const required = metadata.requiredModules\n if (!Array.isArray(required) || required.length === 0) return []\n const missing: string[] = []\n for (const moduleId of required) {\n if (typeof moduleId !== 'string' || moduleId.length === 0) continue\n if (!enabledModuleIds.has(moduleId)) missing.push(moduleId)\n }\n return missing\n}\n\nconst warnedRequiredModuleSkips = new Set<string>()\n\nfunction warnSkippedWidget(metadataId: string, missingModules: string[]) {\n const key = `${metadataId}:${missingModules.join(',')}`\n if (warnedRequiredModuleSkips.has(key)) return\n warnedRequiredModuleSkips.add(key)\n if (process.env.NODE_ENV === 'development') {\n logger.debug('Skipping widget \u2014 required module(s) not enabled', { metadataId, missingModules })\n }\n}\n\nasync function getResolvedEntriesForSpot(spotId: InjectionSpotId): Promise<TableEntry[]> {\n const table = await loadInjectionTable()\n const exactEntries = table.get(spotId) ?? []\n const wildcardEntries: TableEntry[] = []\n\n for (const [candidateSpotId, candidateEntries] of table.entries()) {\n if (candidateSpotId === spotId) continue\n if (!candidateSpotId.includes('*')) continue\n if (!matchWildcardPattern(spotId, candidateSpotId)) continue\n wildcardEntries.push(...candidateEntries)\n }\n\n const dedupedEntries = new Map<string, TableEntry>()\n for (const entry of [...exactEntries, ...wildcardEntries]) {\n const cacheKey = `${entry.moduleId}:${entry.widgetId}`\n const previous = dedupedEntries.get(cacheKey)\n if (!previous || (entry.priority ?? 0) > (previous.priority ?? 0)) {\n dedupedEntries.set(cacheKey, entry)\n }\n }\n\n return Array.from(dedupedEntries.values()).sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0))\n}\n\nexport async function loadAllInjectionWidgets(): Promise<LoadedInjectionWidget[]> {\n const widgetEntries = await loadWidgetEntries()\n const enabledModuleIds = getEnabledModuleIdsForInjection()\n const loaded = await Promise.all(\n widgetEntries.map(async (entry) => {\n const module = await loadEntry(entry)\n if (!isLoadedInjectionWidget(module)) return null\n const missing = widgetMissingRequiredModules(module.metadata, enabledModuleIds)\n if (missing.length > 0) {\n warnSkippedWidget(module.metadata.id, missing)\n return null\n }\n return { ...module, moduleId: entry.moduleId, key: entry.key }\n })\n )\n const byId = new Map<string, LoadedInjectionWidget>()\n for (const widget of loaded) {\n if (!widget) continue\n if (!byId.has(widget.metadata.id)) {\n byId.set(widget.metadata.id, widget)\n }\n }\n return Array.from(byId.values())\n}\n\nexport async function loadInjectionWidgetById(widgetId: string): Promise<LoadedInjectionWidget | null> {\n const enabledModuleIds = getEnabledModuleIdsForInjection()\n const hinted = await tryLoadHintedWidgetById<LoadedInjectionWidget>(widgetId, isLoadedInjectionWidget, enabledModuleIds)\n if (hinted.resolved) return hinted.widget\n\n const index = await loadWidgetLookupIndex()\n const widget = index.widgetsById.get(widgetId)\n return widget ? applyRequiredModuleGate(widget, enabledModuleIds) : null\n}\n\nexport async function loadInjectionDataWidgetById(widgetId: string): Promise<LoadedInjectionDataWidget | null> {\n const enabledModuleIds = getEnabledModuleIdsForInjection()\n const hinted = await tryLoadHintedWidgetById<LoadedInjectionDataWidget>(widgetId, isLoadedInjectionDataWidget, enabledModuleIds)\n if (hinted.resolved) return hinted.widget\n\n const index = await loadWidgetLookupIndex()\n const widget = index.dataWidgetsById.get(widgetId)\n return widget ? applyRequiredModuleGate(widget, enabledModuleIds) : null\n}\n\nexport async function loadInjectionWidgetsForSpot(spotId: InjectionSpotId): Promise<LoadedInjectionWidget[]> {\n const entries = await getResolvedEntriesForSpot(spotId)\n const widgets: LoadedInjectionWidget[] = []\n for (const { widgetId, placement, priority } of entries) {\n const widget = await loadInjectionWidgetById(widgetId)\n if (!widget) continue\n const combinedPlacement = placement\n ? { ...placement, priority: typeof priority === 'number' ? priority : 0 }\n : { priority: typeof priority === 'number' ? priority : 0 }\n widgets.push({ ...widget, placement: combinedPlacement })\n }\n return widgets\n}\n\nexport async function loadInjectionDataWidgetsForSpot(spotId: InjectionSpotId): Promise<LoadedInjectionDataWidget[]> {\n const entries = await getResolvedEntriesForSpot(spotId)\n const widgets: LoadedInjectionDataWidget[] = []\n for (const { widgetId, placement, priority } of entries) {\n const widget = await loadInjectionDataWidgetById(widgetId)\n if (!widget) continue\n const combinedPlacement = placement\n ? { ...placement, priority: typeof priority === 'number' ? priority : 0 }\n : { priority: typeof priority === 'number' ? priority : 0 }\n widgets.push({ ...widget, placement: combinedPlacement })\n }\n return widgets\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/shared",
3
- "version": "0.8.1-develop.7452.1.5e211edd51",
3
+ "version": "0.8.1-develop.7455.1.8f7e53badb",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -121,7 +121,7 @@
121
121
  "@mikro-orm/core": "^7.1.14",
122
122
  "@mikro-orm/decorators": "^7.1.14",
123
123
  "@mikro-orm/postgresql": "^7.1.14",
124
- "@open-mercato/cache": "0.8.1-develop.7452.1.5e211edd51",
124
+ "@open-mercato/cache": "0.8.1-develop.7455.1.8f7e53badb",
125
125
  "@types/html-to-text": "^9.0.4",
126
126
  "@types/sanitize-html": "^2.16.1",
127
127
  "dotenv": "^17.4.2",
@@ -62,7 +62,8 @@ describe('partitioned bootstrap registration', () => {
62
62
  })
63
63
 
64
64
  it('keeps API-only bootstrap from replacing core injection widgets', async () => {
65
- const apiBootstrap = createBootstrap(emptyBootstrapData, {
65
+ const entry = { moduleId: 'example', key: 'example:widget', source: 'app' as const, loader: async () => ({}) as never }
66
+ const apiBootstrap = createBootstrap({ ...emptyBootstrapData, injectionWidgetEntries: [entry] }, {
66
67
  registrationKey: 'api-only',
67
68
  skipUiRegistries: true,
68
69
  skipCoreInjectionWidgets: true,
@@ -71,11 +72,22 @@ describe('partitioned bootstrap registration', () => {
71
72
  apiBootstrap()
72
73
  await waitForAsyncRegistration()
73
74
 
74
- expect(registerCoreInjectionWidgetsMock).not.toHaveBeenCalled()
75
- // The raw widget entries still travel with the tables so a `key`-spelled injection
76
- // override resolves to the `widgetId` the slots reference (#5152), even though this
77
- // bootstrap deliberately skips registering the widgets themselves.
78
- expect(registerCoreInjectionTablesMock).toHaveBeenCalledWith([], [])
75
+ // The API partition still contributes its entries — module API routes resolve widget
76
+ // contributions on the request path — but in `merge` mode, so it can never shrink a
77
+ // registry the full page bootstrap already published in the same process.
78
+ expect(registerCoreInjectionWidgetsMock).toHaveBeenCalledWith([entry], { mode: 'merge' })
79
+ // The raw widget entries also travel with the tables so a `key`-spelled injection
80
+ // override resolves to the `widgetId` the slots reference (#5152).
81
+ expect(registerCoreInjectionTablesMock).toHaveBeenCalledWith([], [entry])
79
82
  expect(registerEnabledModuleIdsMock).toHaveBeenCalledTimes(1)
80
83
  })
84
+
85
+ it('lets the full bootstrap replace the registry outright', async () => {
86
+ const fullBootstrap = createBootstrap(emptyBootstrapData, { registrationKey: 'full-only' })
87
+
88
+ fullBootstrap()
89
+ await waitForAsyncRegistration()
90
+
91
+ expect(registerCoreInjectionWidgetsMock).toHaveBeenCalledWith([], undefined)
92
+ })
81
93
  })
@@ -143,9 +143,14 @@ async function registerWidgetsAndOptionalPackages(data: BootstrapData, options:
143
143
  // browser-facing UI registries. API-only bootstraps avoid loading @open-mercato/ui.
144
144
  try {
145
145
  const coreInjection = await import('@open-mercato/core/modules/widgets/lib/injection')
146
- if (!options.skipCoreInjectionWidgets) {
147
- coreInjection.registerCoreInjectionWidgets(data.injectionWidgetEntries)
148
- }
146
+ // A partitioned runtime still needs its own entries — server-side resolvers read this
147
+ // registry on request paths — it just must not shrink what a fuller partition already
148
+ // published in the same process. Merge does both; skipping registration altogether left
149
+ // API-only processes with an unregistered registry that throws on first read.
150
+ coreInjection.registerCoreInjectionWidgets(
151
+ data.injectionWidgetEntries,
152
+ options.skipCoreInjectionWidgets ? { mode: 'merge' } : undefined,
153
+ )
149
154
  coreInjection.registerCoreInjectionTables(data.injectionTables, data.injectionWidgetEntries)
150
155
  coreInjection.registerEnabledModuleIds(
151
156
  data.modules.map((module) => module.id).filter((id): id is string => typeof id === 'string' && id.length > 0),
@@ -73,7 +73,11 @@ export interface BootstrapOptions {
73
73
  registrationKey?: string
74
74
  /** Skip browser-facing dashboard/injection registries in API-only runtimes. */
75
75
  skipUiRegistries?: boolean
76
- /** Keep an existing core injection-widget registry intact in partitioned runtimes. */
76
+ /**
77
+ * Keep an existing core injection-widget registry intact in partitioned runtimes: this
78
+ * bootstrap's entries are merged in rather than published as the whole registry, so a
79
+ * partition that registers second never shrinks what the first one already exposed.
80
+ */
77
81
  skipCoreInjectionWidgets?: boolean
78
82
  onRegistrationComplete?: () => void
79
83
  appDiRegistrar?: AppDiRegistrar
@@ -7,7 +7,7 @@ import {
7
7
  isCommandInterceptorError,
8
8
  } from '@open-mercato/shared/lib/commands'
9
9
  import { registerCommandInterceptors } from '@open-mercato/shared/lib/commands/command-interceptor-store'
10
- import type { CommandInterceptor } from '@open-mercato/shared/lib/commands/command-interceptor'
10
+ import type { CommandInterceptor, CommandInterceptorContext } from '@open-mercato/shared/lib/commands/command-interceptor'
11
11
 
12
12
  describe('CommandBus', () => {
13
13
  afterEach(() => {
@@ -296,6 +296,57 @@ describe('CommandBus', () => {
296
296
  )
297
297
  })
298
298
 
299
+ it('defers opted-in before snapshots until after the transactional write guard', async () => {
300
+ const calls: string[] = []
301
+ const transactionalEm = { id: 'transaction-em' }
302
+ const buildLogMock = jest.fn(() => ({
303
+ actionLabel: 'Transaction snapshot',
304
+ resourceKind: 'test',
305
+ resourceId: 'transactional',
306
+ }))
307
+ const prepare = jest.fn(async (_input, ctx) => {
308
+ calls.push('prepare')
309
+ expect(ctx.transactionalEm).toBe(transactionalEm)
310
+ return { before: { state: 'locked-before' } }
311
+ })
312
+
313
+ registerCommand({
314
+ id: 'test.command.transactional-snapshot',
315
+ prepare,
316
+ prepareSnapshotInsideTransaction: true,
317
+ execute: jest.fn(async (_input, ctx) => {
318
+ calls.push('execute')
319
+ await ctx.beforeTransactionalWrite?.(transactionalEm as never)
320
+ calls.push('mutate')
321
+ return { ok: true }
322
+ }),
323
+ buildLog: buildLogMock,
324
+ })
325
+
326
+ const logMock = jest.fn(async () => ({ id: 'transaction-log' }))
327
+ const container = createContainer({ injectionMode: InjectionMode.CLASSIC })
328
+ container.register({ actionLogService: asValue({ log: logMock }) })
329
+ const bus = new CommandBus()
330
+ const ctx = {
331
+ container,
332
+ auth: { sub: 'user-transaction', tenantId: 'tenant-transaction', orgId: null },
333
+ organizationScope: null,
334
+ selectedOrganizationId: null,
335
+ organizationIds: null,
336
+ beforeTransactionalWrite: jest.fn(async () => {
337
+ calls.push('lock')
338
+ }),
339
+ }
340
+
341
+ await bus.execute('test.command.transactional-snapshot', { input: {}, ctx })
342
+
343
+ expect(calls).toEqual(['execute', 'lock', 'prepare', 'mutate'])
344
+ expect(prepare).toHaveBeenCalledTimes(1)
345
+ expect(buildLogMock).toHaveBeenCalledWith(expect.objectContaining({
346
+ snapshots: { before: { state: 'locked-before' }, after: undefined },
347
+ }))
348
+ })
349
+
299
350
  it('loads a command file lazily before execution', async () => {
300
351
  const execute = jest.fn(async () => ({ ok: true }))
301
352
  registerCommandLoaders([
@@ -538,6 +589,54 @@ describe('CommandBus', () => {
538
589
  })
539
590
 
540
591
  describe('interceptor rejections', () => {
592
+ it('passes the original request to beforeExecute for preflight version checks', async () => {
593
+ registerCommand({ id: 'test.request-context', execute: jest.fn(async () => ({ ok: true })) })
594
+ const beforeExecute = jest.fn(async (_input: unknown, context: { request?: Request | null }) => {
595
+ expect(context.request?.headers.get('x-om-ext-optimistic-lock-expected-updated-at')).toBe('2026-09-29T11:00:00.000Z')
596
+ return { ok: false, status: 422 }
597
+ })
598
+ registerCommandInterceptors([{ moduleId: 'test', interceptors: [{
599
+ id: 'test.request-context-interceptor', targetCommand: 'test.request-context', beforeExecute,
600
+ }] }])
601
+ const request = new Request('http://localhost/test', { headers: { 'x-om-ext-optimistic-lock-expected-updated-at': '2026-09-29T11:00:00.000Z' } })
602
+ const container = createContainer({ injectionMode: InjectionMode.CLASSIC })
603
+ const bus = new CommandBus()
604
+ await expect(bus.execute('test.request-context', { input: {}, ctx: {
605
+ container,
606
+ auth: { sub: 'user-1', tenantId: 'tenant-1', orgId: null },
607
+ organizationScope: null,
608
+ selectedOrganizationId: null,
609
+ organizationIds: null,
610
+ request,
611
+ } })).rejects.toMatchObject({ status: 422 })
612
+ expect(beforeExecute).toHaveBeenCalledTimes(1)
613
+ })
614
+
615
+ it('passes request and organization scope to both execution lifecycle hooks', async () => {
616
+ registerCommand({ id: 'test.lifecycle-context', execute: async () => ({ ok: true }) })
617
+ const request = new Request('http://localhost/test')
618
+ const organizationScope = { selectedId: 'parent', filterIds: ['parent', 'child'], allowedIds: ['parent', 'child'], tenantId: 'tenant-1' }
619
+ const beforeExecute = jest.fn(async (_input: unknown, context: CommandInterceptorContext) => {
620
+ expect(context.request).toBe(request)
621
+ expect(context.organizationScope).toBe(organizationScope)
622
+ return { ok: true }
623
+ })
624
+ const afterExecute = jest.fn(async (_input: unknown, _result: unknown, context: CommandInterceptorContext) => {
625
+ expect(context.request).toBe(request)
626
+ expect(context.organizationScope).toBe(organizationScope)
627
+ })
628
+ registerCommandInterceptors([{ moduleId: 'test', interceptors: [{
629
+ id: 'test.lifecycle-context-interceptor', targetCommand: 'test.lifecycle-context', beforeExecute, afterExecute,
630
+ }] }])
631
+ await new CommandBus().execute('test.lifecycle-context', { input: {}, ctx: {
632
+ container: createContainer({ injectionMode: InjectionMode.CLASSIC }),
633
+ auth: { sub: 'user-1', tenantId: 'tenant-1', orgId: 'parent' },
634
+ organizationScope, selectedOrganizationId: 'parent', organizationIds: ['parent', 'child'], request,
635
+ } })
636
+ expect(beforeExecute).toHaveBeenCalledTimes(1)
637
+ expect(afterExecute).toHaveBeenCalledTimes(1)
638
+ })
639
+
541
640
  const blockingInterceptor = (result: Record<string, unknown>): CommandInterceptor => ({
542
641
  id: 'test.block',
543
642
  targetCommand: 'test.*',