@wildo-ai/saas-backend-lib 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/base-classes/services/controllers/controller-resource.backend.service.d.ts +14 -3
- package/dist/esm/base-classes/services/controllers/controller-resource.backend.service.d.ts.map +1 -1
- package/dist/esm/base-classes/services/controllers/controller-resource.backend.service.js +115 -22
- package/dist/esm/base-classes/services/controllers/controller-resource.backend.service.js.map +1 -1
- package/dist/esm/base-classes/services/controllers/controllers-registry-handler.backend.service.d.ts +3 -1
- package/dist/esm/base-classes/services/controllers/controllers-registry-handler.backend.service.d.ts.map +1 -1
- package/dist/esm/base-classes/services/controllers/controllers-registry-handler.backend.service.js +8 -3
- package/dist/esm/base-classes/services/controllers/controllers-registry-handler.backend.service.js.map +1 -1
- package/dist/esm/base-classes/services/repositories/field-clearing-partition.backend.d.ts +11 -0
- package/dist/esm/base-classes/services/repositories/field-clearing-partition.backend.d.ts.map +1 -1
- package/dist/esm/base-classes/services/repositories/field-clearing-partition.backend.js +11 -2
- package/dist/esm/base-classes/services/repositories/field-clearing-partition.backend.js.map +1 -1
- package/dist/esm/base-classes/services/repositories/http-api/http-api-resource-repository.backend.service.d.ts +1 -0
- package/dist/esm/base-classes/services/repositories/http-api/http-api-resource-repository.backend.service.d.ts.map +1 -1
- package/dist/esm/base-classes/services/repositories/http-api/http-api-resource-repository.backend.service.js +6 -1
- package/dist/esm/base-classes/services/repositories/http-api/http-api-resource-repository.backend.service.js.map +1 -1
- package/dist/esm/base-classes/services/repositories/http-api/odata-query-compiler.backend.d.ts.map +1 -1
- package/dist/esm/base-classes/services/repositories/http-api/odata-query-compiler.backend.js +16 -0
- package/dist/esm/base-classes/services/repositories/http-api/odata-query-compiler.backend.js.map +1 -1
- package/dist/esm/base-classes/services/repositories/infrastructure-tier-contract.backend.d.ts +20 -6
- package/dist/esm/base-classes/services/repositories/infrastructure-tier-contract.backend.d.ts.map +1 -1
- package/dist/esm/base-classes/services/repositories/infrastructure-tier-contract.backend.js +88 -32
- package/dist/esm/base-classes/services/repositories/infrastructure-tier-contract.backend.js.map +1 -1
- package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-repository.backend.service.d.ts +3 -1
- package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-repository.backend.service.d.ts.map +1 -1
- package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-repository.backend.service.js +32 -7
- package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-repository.backend.service.js.map +1 -1
- package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-source.backend.port.d.ts +8 -5
- package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-source.backend.port.d.ts.map +1 -1
- package/dist/esm/base-classes/services/repositories/introspection/introspection-resource-source.backend.port.js.map +1 -1
- package/dist/esm/base-classes/services/repositories/postgresql-resource-repository.backend.service.d.ts +31 -0
- package/dist/esm/base-classes/services/repositories/postgresql-resource-repository.backend.service.d.ts.map +1 -1
- package/dist/esm/base-classes/services/repositories/postgresql-resource-repository.backend.service.js +127 -5
- package/dist/esm/base-classes/services/repositories/postgresql-resource-repository.backend.service.js.map +1 -1
- package/dist/esm/base-classes/services/repositories/repositories-registry-handler.backend.service.d.ts +71 -1
- package/dist/esm/base-classes/services/repositories/repositories-registry-handler.backend.service.d.ts.map +1 -1
- package/dist/esm/base-classes/services/repositories/repositories-registry-handler.backend.service.js +62 -2
- package/dist/esm/base-classes/services/repositories/repositories-registry-handler.backend.service.js.map +1 -1
- package/dist/esm/base-classes/services/repositories/repository-resource.backend.service.d.ts +21 -0
- package/dist/esm/base-classes/services/repositories/repository-resource.backend.service.d.ts.map +1 -1
- package/dist/esm/base-classes/services/repositories/repository-resource.backend.service.js +126 -4
- package/dist/esm/base-classes/services/repositories/repository-resource.backend.service.js.map +1 -1
- package/dist/esm/base-classes/services/services/resource-operation-implementation-coverage.backend.utils.d.ts +3 -26
- package/dist/esm/base-classes/services/services/resource-operation-implementation-coverage.backend.utils.d.ts.map +1 -1
- package/dist/esm/base-classes/services/services/resource-operation-implementation-coverage.backend.utils.js +3 -68
- package/dist/esm/base-classes/services/services/resource-operation-implementation-coverage.backend.utils.js.map +1 -1
- package/dist/esm/base-classes/services/services/services-registry-handler.backend.service.d.ts.map +1 -1
- package/dist/esm/base-classes/services/services/services-registry-handler.backend.service.js +4 -2
- package/dist/esm/base-classes/services/services/services-registry-handler.backend.service.js.map +1 -1
- package/dist/esm/billing/services/custom-impl/billing-account.mutations.custom-impl.backend.service.js +6 -6
- package/dist/esm/billing/services/custom-impl/billing-account.mutations.custom-impl.backend.service.js.map +1 -1
- package/dist/esm/compliance/audit-trails/services/audit-event-dispatcher.backend.service.d.ts +38 -0
- package/dist/esm/compliance/audit-trails/services/audit-event-dispatcher.backend.service.d.ts.map +1 -1
- package/dist/esm/compliance/audit-trails/services/audit-event-dispatcher.backend.service.js +40 -0
- package/dist/esm/compliance/audit-trails/services/audit-event-dispatcher.backend.service.js.map +1 -1
- package/dist/esm/compliance/audit-trails/services/audit-logs.backend.service.d.ts +20 -0
- package/dist/esm/compliance/audit-trails/services/audit-logs.backend.service.d.ts.map +1 -1
- package/dist/esm/compliance/audit-trails/services/audit-logs.backend.service.js +28 -0
- package/dist/esm/compliance/audit-trails/services/audit-logs.backend.service.js.map +1 -1
- package/dist/esm/compliance/audit-trails/services/siem-export-config-cache-invalidation.custom-impl.backend.service.d.ts +12 -0
- package/dist/esm/compliance/audit-trails/services/siem-export-config-cache-invalidation.custom-impl.backend.service.d.ts.map +1 -0
- package/dist/esm/compliance/audit-trails/services/siem-export-config-cache-invalidation.custom-impl.backend.service.js +118 -0
- package/dist/esm/compliance/audit-trails/services/siem-export-config-cache-invalidation.custom-impl.backend.service.js.map +1 -0
- package/dist/esm/compliance/audit-trails/services/siem-webhook-delivery.backend.service.d.ts.map +1 -1
- package/dist/esm/compliance/audit-trails/services/siem-webhook-delivery.backend.service.js +8 -5
- package/dist/esm/compliance/audit-trails/services/siem-webhook-delivery.backend.service.js.map +1 -1
- package/dist/esm/compliance/audit-trails/services/utils/ocsf-activity-mapping.backend.utils.d.ts.map +1 -1
- package/dist/esm/compliance/audit-trails/services/utils/ocsf-activity-mapping.backend.utils.js +14 -0
- package/dist/esm/compliance/audit-trails/services/utils/ocsf-activity-mapping.backend.utils.js.map +1 -1
- package/dist/esm/config/custom/application-config-controller.backend.service.d.ts.map +1 -1
- package/dist/esm/config/custom/application-config-controller.backend.service.js +7 -1
- package/dist/esm/config/custom/application-config-controller.backend.service.js.map +1 -1
- package/dist/esm/config/models/app-config.backend.schemas.d.ts +4 -0
- package/dist/esm/config/models/app-config.backend.schemas.d.ts.map +1 -1
- package/dist/esm/config/services/app-configuration.backend.utils.d.ts +10 -1
- package/dist/esm/config/services/app-configuration.backend.utils.d.ts.map +1 -1
- package/dist/esm/config/services/app-configuration.backend.utils.js +13 -2
- package/dist/esm/config/services/app-configuration.backend.utils.js.map +1 -1
- package/dist/esm/data-seeding/services/shared/data-seed-scope-iteration.shared.utils.d.ts +2 -2
- package/dist/esm/data-seeding/services/shared/data-seed-scope-iteration.shared.utils.js +2 -2
- package/dist/esm/data-seeding/services/shared/data-seed-scope-iteration.shared.utils.js.map +1 -1
- package/dist/esm/databases/models/filter.backend.schemas.js +2 -2
- package/dist/esm/databases/models/filter.backend.schemas.js.map +1 -1
- package/dist/esm/databases/services/aggregation/aggregation-authorization.backend.service.d.ts.map +1 -1
- package/dist/esm/databases/services/aggregation/aggregation-authorization.backend.service.js +20 -1
- package/dist/esm/databases/services/aggregation/aggregation-authorization.backend.service.js.map +1 -1
- package/dist/esm/databases/services/redis.service.d.ts +27 -0
- package/dist/esm/databases/services/redis.service.d.ts.map +1 -1
- package/dist/esm/databases/services/redis.service.js +29 -0
- package/dist/esm/databases/services/redis.service.js.map +1 -1
- package/dist/esm/errors/services/error-handler.backend.service.d.ts.map +1 -1
- package/dist/esm/errors/services/error-handler.backend.service.js +5 -0
- package/dist/esm/errors/services/error-handler.backend.service.js.map +1 -1
- package/dist/esm/external-data/controllers/external-data-admin.backend.contracts.d.ts +41 -0
- package/dist/esm/external-data/controllers/external-data-admin.backend.contracts.d.ts.map +1 -0
- package/dist/esm/external-data/controllers/external-data-admin.backend.contracts.js +40 -0
- package/dist/esm/external-data/controllers/external-data-admin.backend.contracts.js.map +1 -0
- package/dist/esm/external-data/controllers/external-data-admin.backend.controller.d.ts +64 -0
- package/dist/esm/external-data/controllers/external-data-admin.backend.controller.d.ts.map +1 -0
- package/dist/esm/external-data/controllers/external-data-admin.backend.controller.js +193 -0
- package/dist/esm/external-data/controllers/external-data-admin.backend.controller.js.map +1 -0
- package/dist/esm/external-data/external-data-pipeline-run.backend.service.d.ts +13 -2
- package/dist/esm/external-data/external-data-pipeline-run.backend.service.d.ts.map +1 -1
- package/dist/esm/external-data/external-data-pipeline-run.backend.service.js +8 -2
- package/dist/esm/external-data/external-data-pipeline-run.backend.service.js.map +1 -1
- package/dist/esm/external-data/external-data-transform.backend.d.ts.map +1 -1
- package/dist/esm/external-data/external-data-transform.backend.js +7 -1
- package/dist/esm/external-data/external-data-transform.backend.js.map +1 -1
- package/dist/esm/external-data/models/external-data-pipeline-state.backend.schemas.d.ts +2 -0
- package/dist/esm/external-data/models/external-data-pipeline-state.backend.schemas.d.ts.map +1 -1
- package/dist/esm/external-data/models/external-data-pipeline-state.backend.schemas.js +13 -0
- package/dist/esm/external-data/models/external-data-pipeline-state.backend.schemas.js.map +1 -1
- package/dist/esm/external-providers/services/external-providers-registry.backend.service.d.ts +14 -0
- package/dist/esm/external-providers/services/external-providers-registry.backend.service.d.ts.map +1 -1
- package/dist/esm/external-providers/services/external-providers-registry.backend.service.js +6 -0
- package/dist/esm/external-providers/services/external-providers-registry.backend.service.js.map +1 -1
- package/dist/esm/files/controllers/file.controller.d.ts +24 -1
- package/dist/esm/files/controllers/file.controller.d.ts.map +1 -1
- package/dist/esm/files/controllers/file.controller.js +66 -2
- package/dist/esm/files/controllers/file.controller.js.map +1 -1
- package/dist/esm/files/handlers/file-upload-route-locals.backend.types.d.ts +17 -0
- package/dist/esm/files/handlers/file-upload-route-locals.backend.types.d.ts.map +1 -0
- package/dist/esm/files/handlers/file-upload-route-locals.backend.types.js +1 -0
- package/dist/esm/files/handlers/file-upload-route-locals.backend.types.js.map +1 -0
- package/dist/esm/files/handlers/resource-file-upload-handler.backend.service.d.ts +5 -1
- package/dist/esm/files/handlers/resource-file-upload-handler.backend.service.d.ts.map +1 -1
- package/dist/esm/files/handlers/resource-file-upload-handler.backend.service.js +42 -3
- package/dist/esm/files/handlers/resource-file-upload-handler.backend.service.js.map +1 -1
- package/dist/esm/files/services/file-operation-handler.backend.service.d.ts +18 -0
- package/dist/esm/files/services/file-operation-handler.backend.service.d.ts.map +1 -1
- package/dist/esm/files/services/file-operation-handler.backend.service.js +36 -28
- package/dist/esm/files/services/file-operation-handler.backend.service.js.map +1 -1
- package/dist/esm/files/services/file-upload-grant.backend.service.d.ts +163 -0
- package/dist/esm/files/services/file-upload-grant.backend.service.d.ts.map +1 -0
- package/dist/esm/files/services/file-upload-grant.backend.service.js +442 -0
- package/dist/esm/files/services/file-upload-grant.backend.service.js.map +1 -0
- package/dist/esm/files/services/files.backend.service.d.ts +19 -0
- package/dist/esm/files/services/files.backend.service.d.ts.map +1 -1
- package/dist/esm/files/services/files.backend.service.js +78 -15
- package/dist/esm/files/services/files.backend.service.js.map +1 -1
- package/dist/esm/files/utils/file-uploader.utils.d.ts.map +1 -1
- package/dist/esm/files/utils/file-uploader.utils.js +12 -0
- package/dist/esm/files/utils/file-uploader.utils.js.map +1 -1
- package/dist/esm/flows-actors/a2a/services/a2a-task-cancellation.backend.service.js +5 -5
- package/dist/esm/flows-actors/a2a/services/a2a-task-cancellation.backend.service.js.map +1 -1
- package/dist/esm/flows-actors/services/agents.backend.service.d.ts +79 -0
- package/dist/esm/flows-actors/services/agents.backend.service.d.ts.map +1 -1
- package/dist/esm/flows-actors/services/agents.backend.service.js +319 -42
- package/dist/esm/flows-actors/services/agents.backend.service.js.map +1 -1
- package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.d.ts +151 -41
- package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.d.ts.map +1 -1
- package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.js +300 -139
- package/dist/esm/flows-actors/utils/acp-session-runner.backend.utils.js.map +1 -1
- package/dist/esm/index.d.ts +4 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +14 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/inject/models/container.d.ts.map +1 -1
- package/dist/esm/inject/models/container.js +4 -0
- package/dist/esm/inject/models/container.js.map +1 -1
- package/dist/esm/inject/models/service-types.d.ts +3 -0
- package/dist/esm/inject/models/service-types.d.ts.map +1 -1
- package/dist/esm/inject/models/service-types.js +2 -0
- package/dist/esm/inject/models/service-types.js.map +1 -1
- package/dist/esm/mcp/controllers/mcp.controller.d.ts.map +1 -1
- package/dist/esm/mcp/controllers/mcp.controller.js +12 -4
- package/dist/esm/mcp/controllers/mcp.controller.js.map +1 -1
- package/dist/esm/mcp/models/mcp-tool.backend.schemas.d.ts +34 -0
- package/dist/esm/mcp/models/mcp-tool.backend.schemas.d.ts.map +1 -1
- package/dist/esm/mcp/models/mcp-tool.backend.schemas.js +42 -0
- package/dist/esm/mcp/models/mcp-tool.backend.schemas.js.map +1 -1
- package/dist/esm/mcp/services/mcp-server.backend.service.d.ts +29 -1
- package/dist/esm/mcp/services/mcp-server.backend.service.d.ts.map +1 -1
- package/dist/esm/mcp/services/mcp-server.backend.service.js +131 -3
- package/dist/esm/mcp/services/mcp-server.backend.service.js.map +1 -1
- package/dist/esm/mcp/services/mcp-tool-adapter.backend.service.d.ts +64 -2
- package/dist/esm/mcp/services/mcp-tool-adapter.backend.service.d.ts.map +1 -1
- package/dist/esm/mcp/services/mcp-tool-adapter.backend.service.js +130 -6
- package/dist/esm/mcp/services/mcp-tool-adapter.backend.service.js.map +1 -1
- package/dist/esm/notifications/services/notifications-dispatcher.backend.service.d.ts +9 -0
- package/dist/esm/notifications/services/notifications-dispatcher.backend.service.d.ts.map +1 -1
- package/dist/esm/notifications/services/notifications-dispatcher.backend.service.js +11 -36
- package/dist/esm/notifications/services/notifications-dispatcher.backend.service.js.map +1 -1
- package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.backend.service.d.ts.map +1 -1
- package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.backend.service.js +9 -7
- package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.backend.service.js.map +1 -1
- package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.utils.d.ts +5 -5
- package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.utils.d.ts.map +1 -1
- package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.utils.js +14 -13
- package/dist/esm/notifications/webhooks/outbound/batches/m2m-webhook-delivery.batch.utils.js.map +1 -1
- package/dist/esm/notifications/webhooks/outbound/m2m-webhook-signed-request.backend.utils.d.ts +2 -1
- package/dist/esm/notifications/webhooks/outbound/m2m-webhook-signed-request.backend.utils.d.ts.map +1 -1
- package/dist/esm/notifications/webhooks/outbound/m2m-webhook-signed-request.backend.utils.js +2 -2
- package/dist/esm/notifications/webhooks/outbound/m2m-webhook-signed-request.backend.utils.js.map +1 -1
- package/dist/esm/notifications/webhooks/outbound/services/webhook-config-test-endpoint.custom-impl.backend.service.d.ts.map +1 -1
- package/dist/esm/notifications/webhooks/outbound/services/webhook-config-test-endpoint.custom-impl.backend.service.js +9 -7
- package/dist/esm/notifications/webhooks/outbound/services/webhook-config-test-endpoint.custom-impl.backend.service.js.map +1 -1
- package/dist/esm/organizations/services/organization-unit-custom-implementation.backend.service.d.ts.map +1 -1
- package/dist/esm/organizations/services/organization-unit-custom-implementation.backend.service.js +4 -1
- package/dist/esm/organizations/services/organization-unit-custom-implementation.backend.service.js.map +1 -1
- package/dist/esm/requests/service/websocket.service.d.ts.map +1 -1
- package/dist/esm/requests/service/websocket.service.js +56 -7
- package/dist/esm/requests/service/websocket.service.js.map +1 -1
- package/dist/esm/resources/models/custom-service-implementations.backend.definition.d.ts.map +1 -1
- package/dist/esm/resources/models/custom-service-implementations.backend.definition.js +2 -0
- package/dist/esm/resources/models/custom-service-implementations.backend.definition.js.map +1 -1
- package/dist/esm/resources/services/resource-id.backend.service.js +3 -3
- package/dist/esm/resources/services/resource-id.backend.service.js.map +1 -1
- package/dist/esm/resources/services/resources-registry.backend.service.js +1 -1
- package/dist/esm/resources/services/resources-registry.backend.service.js.map +1 -1
- package/dist/esm/runtime/services/execution-context-creator.backend.service.d.ts.map +1 -1
- package/dist/esm/runtime/services/execution-context-creator.backend.service.js +13 -7
- package/dist/esm/runtime/services/execution-context-creator.backend.service.js.map +1 -1
- package/dist/esm/runtime/services/start-application.backend.service.d.ts +28 -0
- package/dist/esm/runtime/services/start-application.backend.service.d.ts.map +1 -1
- package/dist/esm/runtime/services/start-application.backend.service.js +82 -0
- package/dist/esm/runtime/services/start-application.backend.service.js.map +1 -1
- package/dist/esm/security/authentications/models/jwt.backend.schemas.d.ts +3 -23
- package/dist/esm/security/authentications/models/jwt.backend.schemas.d.ts.map +1 -1
- package/dist/esm/security/authentications/models/jwt.backend.schemas.js +20 -45
- package/dist/esm/security/authentications/models/jwt.backend.schemas.js.map +1 -1
- package/dist/esm/security/authentications/services/auth-methods/auth-method-management.backend.service.d.ts +9 -1
- package/dist/esm/security/authentications/services/auth-methods/auth-method-management.backend.service.d.ts.map +1 -1
- package/dist/esm/security/authentications/services/auth-methods/auth-method-management.backend.service.js +9 -1
- package/dist/esm/security/authentications/services/auth-methods/auth-method-management.backend.service.js.map +1 -1
- package/dist/esm/security/authentications/services/auth-methods/auth-mfa.backend.service.d.ts.map +1 -1
- package/dist/esm/security/authentications/services/auth-methods/auth-mfa.backend.service.js +5 -3
- package/dist/esm/security/authentications/services/auth-methods/auth-mfa.backend.service.js.map +1 -1
- package/dist/esm/security/authentications/services/auth-token-issuer.backend.service.d.ts +25 -1
- package/dist/esm/security/authentications/services/auth-token-issuer.backend.service.d.ts.map +1 -1
- package/dist/esm/security/authentications/services/auth-token-issuer.backend.service.js +55 -4
- package/dist/esm/security/authentications/services/auth-token-issuer.backend.service.js.map +1 -1
- package/dist/esm/security/authorizations/initiator-organization-confinement.backend.d.ts.map +1 -1
- package/dist/esm/security/authorizations/initiator-organization-confinement.backend.js +24 -1
- package/dist/esm/security/authorizations/initiator-organization-confinement.backend.js.map +1 -1
- package/dist/esm/security/authorizations/privileged-access-review.backend.utils.d.ts +117 -0
- package/dist/esm/security/authorizations/privileged-access-review.backend.utils.d.ts.map +1 -0
- package/dist/esm/security/authorizations/privileged-access-review.backend.utils.js +104 -0
- package/dist/esm/security/authorizations/privileged-access-review.backend.utils.js.map +1 -0
- package/dist/esm/security/authorizations/services/authorizations.backend.service.d.ts +101 -6
- package/dist/esm/security/authorizations/services/authorizations.backend.service.d.ts.map +1 -1
- package/dist/esm/security/authorizations/services/authorizations.backend.service.js +210 -30
- package/dist/esm/security/authorizations/services/authorizations.backend.service.js.map +1 -1
- package/dist/esm/security/consumable-tokens/batches/consumable-tokens-cleanup.batch.backend.service.d.ts +18 -1
- package/dist/esm/security/consumable-tokens/batches/consumable-tokens-cleanup.batch.backend.service.d.ts.map +1 -1
- package/dist/esm/security/consumable-tokens/batches/consumable-tokens-cleanup.batch.backend.service.js +110 -74
- package/dist/esm/security/consumable-tokens/batches/consumable-tokens-cleanup.batch.backend.service.js.map +1 -1
- package/dist/esm/security/consumable-tokens/consumable-token-models.exports.d.ts +22 -0
- package/dist/esm/security/consumable-tokens/consumable-token-models.exports.d.ts.map +1 -0
- package/dist/esm/security/consumable-tokens/consumable-token-models.exports.js +22 -0
- package/dist/esm/security/consumable-tokens/consumable-token-models.exports.js.map +1 -0
- package/dist/esm/security/consumable-tokens/models/consumable-tokens.backend.resources-config.schemas.d.ts +17 -3
- package/dist/esm/security/consumable-tokens/models/consumable-tokens.backend.resources-config.schemas.d.ts.map +1 -1
- package/dist/esm/security/consumable-tokens/models/consumable-tokens.backend.resources-config.schemas.js +12 -0
- package/dist/esm/security/consumable-tokens/models/consumable-tokens.backend.resources-config.schemas.js.map +1 -1
- package/dist/esm/security/consumable-tokens/services/consumable-token.backend.service.d.ts +20 -0
- package/dist/esm/security/consumable-tokens/services/consumable-token.backend.service.d.ts.map +1 -1
- package/dist/esm/security/consumable-tokens/services/consumable-token.backend.service.js +58 -5
- package/dist/esm/security/consumable-tokens/services/consumable-token.backend.service.js.map +1 -1
- package/dist/esm/security/jwt/services/jwt.backend.service.d.ts.map +1 -1
- package/dist/esm/security/jwt/services/jwt.backend.service.js +1 -18
- package/dist/esm/security/jwt/services/jwt.backend.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +15 -9
- package/dist/esm/.builder.pid +0 -9
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis.service.js","sourceRoot":"","sources":["../../../../../src/databases/services/redis.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { injectable, inject } from 'inversify';\nimport { createClient, RedisClientType, RedisClientOptions } from 'redis';\nimport { SAAS_SERVICE_TYPES } from '~wildo-backend-lib/inject/models/service-types';\nimport { LoggerService } from '~wildo-backend-lib/monitoring/logger/services/logger.service';\nimport { AppConfigurationService } from '~wildo-backend-lib/config/services/app-configuration.backend.service';\nimport { ErrorBuilderBackendService } from '~wildo-backend-lib/errors/services/error-builder.backend.service';\nimport { describeCaughtError, projectCaughtErrorLogFields } from '~wildo-backend-lib/errors/utils/error-log-level-classification.backend.utils';\nimport { defaultDatabaseConfig } from '~wildo-backend-lib/databases/models/databases.backend.schemas';\nimport { ErrorType, ErrorCustomMessageReference } from '@wildo-ai/saas-models';\n\nexport interface RedisHealthCheck {\n status: 'healthy' | 'unhealthy' | 'connecting';\n latency?: number;\n error?: string;\n memory: {\n used: number;\n peak: number;\n };\n}\n\nexport interface CacheOptions {\n ttl?: number; // Time to live in seconds\n organizationId?: string; // For multi-tenant caching\n}\n\nexport interface SessionData {\n userId: string;\n organizationId: string;\n role: string;\n permissions: string[];\n loginTime: number;\n lastActivity: number;\n}\n\n/**\n * Redis Service\n *\n * Handles Redis operations for:\n * - Caching (application data, API responses)\n * - Session management\n * - Real-time data storage\n * - Multi-tenant data isolation\n * - Rate limiting data\n */\n@injectable()\nexport class RedisService {\n private client: RedisClientType | null = null;\n private isConnecting: boolean = false;\n private connectionRetries: number = 0;\n private readonly maxRetries: number = 5;\n private readonly retryDelay: number = 3000; // 3 seconds\n protected logDebug: (message: string, context?: Record<string, unknown>) => void;\n\n /**\n * Canonical app-level Redis key namespace (Slice 3.6).\n *\n * Sourced from `REDIS_KEY_PREFIX` env (originating from\n * `wildo.saas.config.dataServices.redis.keyPrefix`), surfaced via\n * `appConfigService.config.database.redis.keyPrefix`. Every key the\n * service writes becomes `${appPrefix}org:${orgId}:${key}` or\n * `${appPrefix}global:${key}`.\n *\n * REPLACE semantics: this supersedes the legacy\n * `secrets.database.redis.username`-derived prefix (Slice 3.6 audit\n * decision — single source of truth = `wildo.saas.config.dataServices`).\n * The Redis ACL username remains the auth identity but no longer drives\n * key namespacing.\n *\n * When the app does not author `dataServices.redis.keyPrefix`, the\n * runtime computes `<appSlug>:` as the slug-derived default\n * (ACL-alignment fix, 2026-05-26). The historical literal default\n * `'saas:'` was removed because it was global across every\n * un-authored app and DID NOT align with the per-app ACL grant\n * `~<appSlug>:*` — every Redis op returned `NOPERM` until the\n * schema and the ACL agreed on the slug-derived namespace.\n *\n * ACL invariant (enforced in `connect()`): when an authored value\n * is present, it MUST start with `<appSlug>:` so the ACL grant\n * remains a superset of the keys the backend writes.\n */\n private appPrefix: string = '';\n\n constructor(\n @inject(SAAS_SERVICE_TYPES.Logger) private logger: LoggerService,\n @inject(SAAS_SERVICE_TYPES.AppConfigService) private configService: AppConfigurationService,\n @inject(SAAS_SERVICE_TYPES.ErrorBuilderService) private errorBuilder: ErrorBuilderBackendService\n ) {\n this.logDebug = this.logger.debugByType('RedisService');\n this.logger.setDebugTypeLogging('RedisService', false);\n }\n\n /**\n * Compute the canonical app-level Redis key namespace and validate it\n * against the slug-rooted ACL contract. Run as a connection PRECONDITION\n * (before the retry block) so invalid config fails loud, not silently.\n *\n * Source order (Slice 3.6 + ACL-alignment fix 2026-05-26):\n * 1. `config.database.redis.keyPrefix` — the SaaS app's authored value\n * (from `wildo.saas.config.dataServices.redis.keyPrefix`, threaded\n * via the `REDIS_KEY_PREFIX` env). Empty string is preserved as an\n * explicit no-prefix opt-out (tests / no-prefix workloads).\n * 2. `<appSlug>:` — slug-derived default when unauthored. REPLACES the\n * historical literal `'saas:'` default, which was global across every\n * un-authored app and did NOT match the per-app ACL grant\n * `~<appSlug>:*` (apps-manager scoped the ACL to the slug; the\n * backend wrote `saas:<key>` → every op `NOPERM`).\n *\n * Slice 3 deep-review L-9: sourced from `config.database.redis.keyPrefix`\n * directly (NOT the `{ ...defaults, ...config, ...secrets }` merged blob),\n * so a stray `keyPrefix` in the secrets payload can't shadow the\n * non-secret app config.\n *\n * ACL invariant: an authored `keyPrefix` MUST start with `<appSlug>:` so\n * the `~<appSlug>:*` grant covers every key the backend writes.\n */\n private computeAndValidateAppPrefix(): void {\n const config = this.configService.config;\n const appSlug = this.configService.appSlug;\n const slugDerivedDefault = `${appSlug}:`;\n const authoredKeyPrefix = config.database.redis?.keyPrefix;\n this.appPrefix = authoredKeyPrefix !== undefined\n ? authoredKeyPrefix\n : slugDerivedDefault;\n\n if (this.appPrefix && !this.appPrefix.startsWith(`${appSlug}:`)) {\n throw this.errorBuilder.buildError(\n ErrorType.CONFIGURATION,\n undefined,\n {\n customMessageReference: ErrorCustomMessageReference.CONFIGURATION,\n context: {\n message:\n `dataServices.redis.keyPrefix '${this.appPrefix}' must start with '${appSlug}:' `\n + 'so the per-app ACL grant `~<appSlug>:*` covers it. '\n + `Either omit the field (default '${slugDerivedDefault}' applies) `\n + `or change the authored value to '${slugDerivedDefault}<suffix>'.`,\n authoredKeyPrefix: this.appPrefix,\n appSlug,\n },\n },\n );\n }\n }\n\n /**\n * Connect to Redis\n */\n public async connect(): Promise<RedisClientType> {\n if (this.client && this.client.isReady) {\n return this.client;\n }\n\n if (this.isConnecting) {\n // Wait for existing connection attempt\n return new Promise((resolve, reject) => {\n const checkConnection = () => {\n if (this.client && this.client.isReady) {\n resolve(this.client);\n } else if (!this.isConnecting) {\n reject(new Error('Connection attempt failed'));\n } else {\n setTimeout(checkConnection, 100);\n }\n };\n checkConnection();\n });\n }\n\n this.isConnecting = true;\n\n // Compute + validate the app-level Redis key namespace as a CONFIG\n // PRECONDITION — BEFORE the connection retry block. A keyPrefix that\n // doesn't satisfy the slug-rooted ACL contract is invalid config, not a\n // transient connection failure: it must throw immediately (with its\n // actionable message) and NOT be swallowed by the catch's retry loop.\n // (Once the client connects once and `isReady` flips true, a retry\n // would re-enter connect(), hit the `client.isReady` early-return, and\n // resolve — masking the invalid config.)\n this.computeAndValidateAppPrefix();\n\n try {\n const config = this.configService.config;\n const secrets = this.configService.secrets;\n const redisDefaultConfig = defaultDatabaseConfig.redis;\n\n const redisConfig = {\n ...redisDefaultConfig,\n ...config.database.redis,\n ...secrets.database.redis\n };\n\n if (!redisConfig) {\n const error = this.errorBuilder.buildError(\n ErrorType.CONFIGURATION,\n undefined,\n { customMessageReference: ErrorCustomMessageReference.REDIS_INITIALIZATION_FAILED }\n );\n throw error;\n }\n\n const redisUrl = `redis://${redisConfig.host}:${redisConfig.port}`;\n\n this.logDebug('Connecting to Redis', {\n url: this.maskConnectionString(redisUrl)\n });\n\n // Redis options for SaaS multi-tenant architecture\n // Include username for ACL authentication (required when using app-specific credentials)\n const redisOptions: RedisClientOptions = {\n socket: {\n host: redisConfig.host,\n port: redisConfig.port,\n connectTimeout: redisConfig.connectTimeout || 10000,\n // `connect()` already owns a finite five-attempt policy below. If\n // node-redis also reconnects internally, its first promise can stay\n // pending forever and the service-owned retry budget never advances.\n reconnectStrategy: false,\n },\n username: redisConfig.username,\n password: redisConfig.password,\n database: redisConfig.db\n };\n\n this.client = createClient(redisOptions) as RedisClientType;\n\n // Setup event handlers\n this.setupEventHandlers(this.client, 'main');\n\n // Wait for connection to be ready\n await this.client.connect();\n\n // `this.appPrefix` was already computed + validated as a config\n // precondition before the retry block (see\n // `computeAndValidateAppPrefix`). It is intentionally NOT recomputed\n // here: doing so post-connect put the validation inside the retry\n // catch, where — once the client connected and `isReady` flipped true\n // — a retry would hit the `client.isReady` early-return at the top of\n // connect() and resolve, masking an invalid keyPrefix. Config\n // validation belongs before the connection, not after it.\n\n this.connectionRetries = 0;\n this.isConnecting = false;\n\n this.logDebug('Redis connected successfully', {\n isReady: this.client.isReady,\n isOpen: this.client.isOpen,\n appPrefix: this.appPrefix || '(none)'\n });\n\n return this.client;\n\n } catch (error) {\n this.isConnecting = false;\n this.connectionRetries++;\n\n this.logger.error('Redis connection failed', {\n ...projectCaughtErrorLogFields(error),\n retries: this.connectionRetries,\n maxRetries: this.maxRetries\n });\n\n if (this.connectionRetries < this.maxRetries) {\n this.logDebug(`Retrying Redis connection in ${this.retryDelay}ms...`);\n await this.delay(this.retryDelay);\n return await this.connect();\n } else {\n const connectionError = this.errorBuilder.buildError(\n ErrorType.DATABASE,\n undefined,\n {\n customMessageReference: ErrorCustomMessageReference.REDIS_CONNECTION_FAILED,\n context: {\n retries: this.connectionRetries,\n maxRetries: this.maxRetries,\n originalError: describeCaughtError(error)\n }\n }\n );\n throw connectionError;\n }\n }\n }\n\n /**\n * Disconnect from Redis\n */\n public async disconnect(): Promise<void> {\n this.logDebug('Disconnecting from Redis');\n\n if (this.client) {\n await this.client.quit();\n this.client = null;\n }\n\n this.logDebug('Redis disconnected successfully');\n }\n\n /**\n * Get Redis client\n */\n public getClient(): RedisClientType | null {\n return this.client;\n }\n\n /**\n * Get the canonical app-level Redis key namespace prefix (Slice 3.6\n * + 2026-05-26 ACL-alignment fix).\n *\n * Returns the value of `wildo.saas.config.dataServices.redis.keyPrefix`\n * (e.g., `\"wonder-todos:\"`), surfaced via the `REDIS_KEY_PREFIX` env\n * variable into `config.database.redis.keyPrefix`. When the field is\n * not authored, falls back to `<appSlug>:` (e.g.,\n * `\"wonder-todos:\"` for app `wonder-todos`) — the slug-derived\n * default that aligns with the per-app ACL grant `~<appSlug>:*`.\n *\n * **Slice 3 deep-review L-9 (2026-04-13) — sourcing precedence.** The\n * authoritative source is `config.database.redis.keyPrefix`, NOT the\n * merged `redisConfig.keyPrefix` blob constructed in `connect()`. The\n * connection-options blob spreads `secrets.database.redis` on top of\n * `config.database.redis`, which would let a stray `keyPrefix` in the\n * secrets payload silently shadow the authored config value. The\n * namespace prefix is non-secret authored config, so `connect()`\n * intentionally reads it directly from `config.database.redis?.keyPrefix\n * ?? \\`${configService.appSlug}:\\`` and stores it on `this.appPrefix`\n * before the merged blob is built. Pinned by the L-9 regression test\n * in `redis.service.test.ts`.\n *\n * Used by `WebSocketService` and any other service that must construct\n * Redis keys outside `set/get/del` (e.g., `eval()`, pub/sub channel\n * names, scan patterns) so they share the same namespace as\n * `createTenantKey()`.\n */\n public getAppPrefix(): string {\n return this.appPrefix;\n }\n\n /**\n * Check Redis health\n */\n public async healthCheck(): Promise<RedisHealthCheck> {\n try {\n if (!this.client || !this.client.isReady) {\n return {\n status: 'unhealthy',\n error: 'No active connection',\n memory: { used: 0, peak: 0 }\n };\n }\n\n const startTime = Date.now();\n\n // Perform ping operation\n await this.client.ping();\n\n const latency = Date.now() - startTime;\n\n // Get memory info\n const info = await this.client.info('memory');\n const memoryUsed = this.parseInfoValue(info, 'used_memory');\n const memoryPeak = this.parseInfoValue(info, 'used_memory_peak');\n\n return {\n status: 'healthy',\n latency,\n memory: {\n used: memoryUsed,\n peak: memoryPeak\n }\n };\n\n } catch (error) {\n this.logger.error('Redis health check failed', {\n ...projectCaughtErrorLogFields(error)\n });\n\n return {\n status: 'unhealthy',\n error: describeCaughtError(error),\n memory: { used: 0, peak: 0 }\n };\n }\n }\n\n /**\n * Cache operations with multi-tenant support\n */\n public async set(\n key: string,\n value: any,\n options: CacheOptions = {}\n ): Promise<void> {\n if (!this.client) {\n const error = this.errorBuilder.buildError(\n ErrorType.DATABASE,\n undefined,\n { customMessageReference: ErrorCustomMessageReference.REDIS_CONNECTION_FAILED }\n );\n throw error;\n }\n\n const tenantKey = this.createTenantKey(key, options.organizationId);\n const serializedValue = JSON.stringify(value);\n\n if (options.ttl) {\n await this.client.setEx(tenantKey, options.ttl, serializedValue);\n } else {\n await this.client.set(tenantKey, serializedValue);\n }\n\n this.logDebug('Cache set', { key: tenantKey, ttl: options.ttl });\n }\n\n public async get<T = any>(\n key: string,\n organizationId?: string\n ): Promise<T | null> {\n if (!this.client) {\n return null;\n }\n\n const tenantKey = this.createTenantKey(key, organizationId);\n const value = await this.client.get(tenantKey);\n\n if (value === null) {\n return null;\n }\n\n try {\n return JSON.parse(value as string) as T;\n } catch (error) {\n this.logger.error('Failed to parse cached value', {\n key: tenantKey,\n ...projectCaughtErrorLogFields(error)\n });\n return null;\n }\n }\n\n /**\n * Atomically read a value and delete it in one round-trip (Redis native\n * `GETDEL`, available since Redis 6.2).\n *\n * Symmetric with {@link set} / {@link get}: applies the SAME multi-tenant\n * key scoping AND the SAME `JSON.parse` deserialization, so a value written\n * with `set()` round-trips correctly. Reach for this on single-use tokens —\n * WebAuthn challenges, one-time codes — where the read must consume the\n * value atomically (no get-then-del race, no replay of a still-present\n * token).\n *\n * Do NOT hand-roll this with `eval()` + a raw `GET`/`DEL` Lua script: the\n * raw `eval` path returns the stored bytes verbatim and bypasses the\n * `JSON.parse` that `set()`'s `JSON.stringify` requires, so the caller gets\n * a value wrapped in literal quote characters (`\"\\\"abc\\\"\"` instead of\n * `abc`). That asymmetry silently broke passkey enrollment until this\n * method centralized the contract.\n *\n * Returns `null` if the key is absent (or the stored value can't be parsed).\n */\n public async getDel<T = any>(\n key: string,\n organizationId?: string\n ): Promise<T | null> {\n if (!this.client) {\n return null;\n }\n\n const tenantKey = this.createTenantKey(key, organizationId);\n const value = await this.client.getDel(tenantKey);\n\n if (value === null) {\n return null;\n }\n\n try {\n return JSON.parse(value as string) as T;\n } catch (error) {\n this.logger.error('Failed to parse cached value', {\n key: tenantKey,\n ...projectCaughtErrorLogFields(error)\n });\n return null;\n }\n }\n\n public async del(key: string, organizationId?: string): Promise<void> {\n if (!this.client) {\n const error = this.errorBuilder.buildError(\n ErrorType.DATABASE,\n undefined,\n { customMessageReference: ErrorCustomMessageReference.REDIS_CONNECTION_FAILED }\n );\n throw error;\n }\n\n const tenantKey = this.createTenantKey(key, organizationId);\n await this.client.del(tenantKey);\n\n this.logDebug('Cache deleted', { key: tenantKey });\n }\n\n public async exists(key: string, organizationId?: string): Promise<boolean> {\n if (!this.client) {\n return false;\n }\n\n const tenantKey = this.createTenantKey(key, organizationId);\n const result = await this.client.exists(tenantKey);\n return result === 1;\n }\n\n /**\n * Session management\n */\n public async setSession(\n sessionId: string,\n sessionData: SessionData,\n ttl: number = 86400\n ): Promise<void> {\n const key = `session:${sessionId}`;\n await this.set(key, sessionData, { ttl });\n }\n\n public async getSession(sessionId: string): Promise<SessionData | null> {\n const key = `session:${sessionId}`;\n return await this.get<SessionData>(key);\n }\n\n public async deleteSession(sessionId: string): Promise<void> {\n const key = `session:${sessionId}`;\n await this.del(key);\n }\n\n public async updateSessionActivity(sessionId: string): Promise<void> {\n const session = await this.getSession(sessionId);\n if (session) {\n session.lastActivity = Date.now();\n await this.setSession(sessionId, session);\n }\n }\n\n /**\n * Build a fully-qualified Redis key with app prefix and tenant scope.\n * Use this when constructing keys for `eval()` Lua scripts so they match\n * the keys produced by `set()`/`get()`/`del()`.\n */\n public buildKey(key: string, organizationId?: string): string {\n return this.createTenantKey(key, organizationId);\n }\n\n /**\n * Execute a Lua script atomically on the Redis server.\n * Used for atomic read-modify-write operations (e.g., session updates with version checks).\n *\n * IMPORTANT: Keys passed here must be fully-qualified (use `buildKey()` to construct them).\n * Unlike `set()`/`get()`, this method does NOT apply `createTenantKey()` automatically.\n */\n public async eval(script: string, keys: string[], args: string[]): Promise<any> {\n if (!this.client) {\n const error = this.errorBuilder.buildError(\n ErrorType.DATABASE,\n undefined,\n { customMessageReference: ErrorCustomMessageReference.REDIS_CONNECTION_FAILED }\n );\n throw error;\n }\n return this.client.eval(script, { keys, arguments: args });\n }\n\n /**\n * Rate limiting support\n */\n public async incrementRateLimit(\n identifier: string,\n windowSeconds: number,\n organizationId?: string\n ): Promise<number> {\n if (!this.client) {\n const error = this.errorBuilder.buildError(\n ErrorType.DATABASE,\n undefined,\n { customMessageReference: ErrorCustomMessageReference.REDIS_CONNECTION_FAILED }\n );\n throw error;\n }\n\n const key = this.createTenantKey(`rate_limit:${identifier}`, organizationId);\n\n const current = await this.client.eval(\n `\n local current = redis.call('INCR', KEYS[1])\n local ttl = redis.call('TTL', KEYS[1])\n if ttl < 0 then\n redis.call('EXPIRE', KEYS[1], tonumber(ARGV[1]))\n end\n return current\n `,\n {\n keys: [key],\n arguments: [String(windowSeconds)],\n },\n );\n return typeof current === 'number' ? current : Number(current);\n }\n\n /**\n * Get the current rate limit count for a given identifier without incrementing.\n * Uses the same key format as incrementRateLimit() — callers should NOT manually\n * prepend \"rate_limit:\" since this method does it internally.\n */\n public async getRateLimitCount(\n identifier: string,\n organizationId?: string,\n ): Promise<number> {\n if (!this.client) {\n const error = this.errorBuilder.buildError(\n ErrorType.DATABASE,\n undefined,\n { customMessageReference: ErrorCustomMessageReference.REDIS_CONNECTION_FAILED }\n );\n throw error;\n }\n\n const key = this.createTenantKey(`rate_limit:${identifier}`, organizationId);\n const value = await this.client.get(key);\n if (value === null || typeof value !== 'string') return 0;\n\n const parsed = parseInt(value, 10);\n return isNaN(parsed) ? 0 : parsed;\n }\n\n public async getRateLimitTtl(\n identifier: string,\n organizationId?: string,\n ): Promise<number> {\n if (!this.client) {\n const error = this.errorBuilder.buildError(\n ErrorType.DATABASE,\n undefined,\n { customMessageReference: ErrorCustomMessageReference.REDIS_CONNECTION_FAILED }\n );\n throw error;\n }\n\n const key = this.createTenantKey(`rate_limit:${identifier}`, organizationId);\n const ttl = await this.client.ttl(key);\n return ttl > 0 ? ttl : 0;\n }\n\n /**\n * Create tenant-scoped key with app prefix for ACL isolation.\n *\n * Key format: {appPrefix}{scope}:{key}\n * - appPrefix: canonical app namespace from\n * `wildo.saas.config.dataServices.redis.keyPrefix` (Slice 3.6),\n * surfaced via `REDIS_KEY_PREFIX` env (e.g., `\"wonder-todos:\"`)\n * - scope: \"org:{orgId}\" for org-scoped data, \"global\" for app-global data\n * - key: the actual key name\n *\n * Examples:\n * - \"wonder-todos:org:abc123:cache:users\"\n * - \"wonder-todos:global:session:xyz789\"\n */\n private createTenantKey(key: string, organizationId?: string): string {\n if (organizationId) {\n return `${this.appPrefix}org:${organizationId}:${key}`;\n }\n return `${this.appPrefix}global:${key}`;\n }\n\n /**\n * Setup Redis event handlers\n */\n private setupEventHandlers(client: RedisClientType, clientType: string): void {\n client.on('connect', () => {\n this.logDebug(`Redis ${clientType} client connecting`);\n });\n\n client.on('ready', () => {\n this.logDebug(`Redis ${clientType} client ready`);\n });\n\n client.on('error', (error) => {\n this.logger.error(`Redis ${clientType} client error`, {\n error: error.message\n });\n });\n\n client.on('close', () => {\n this.logger.warn(`Redis ${clientType} client connection closed`);\n });\n\n client.on('reconnecting', () => {\n this.logDebug(`Redis ${clientType} client reconnecting`);\n });\n }\n\n /**\n * Parse value from Redis INFO command output\n */\n private parseInfoValue(info: string, key: string): number {\n const regex = new RegExp(`${key}:(\\\\d+)`);\n const match = info.match(regex);\n return match ? parseInt(match[1]) : 0;\n }\n\n /**\n * Mask sensitive parts of connection string for logging\n */\n private maskConnectionString(url: string): string {\n try {\n const redisUrl = new URL(url);\n if (redisUrl.password) {\n redisUrl.password = '***';\n }\n return redisUrl.toString();\n } catch {\n return url.replace(/:([^@]+)@/, ':***@');\n }\n }\n\n /**\n * Delay utility for connection retries\n */\n private delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"redis.service.js","sourceRoot":"","sources":["../../../../../src/databases/services/redis.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { injectable, inject } from 'inversify';\nimport { createClient, RedisClientType, RedisClientOptions } from 'redis';\nimport { SAAS_SERVICE_TYPES } from '~wildo-backend-lib/inject/models/service-types';\nimport { LoggerService } from '~wildo-backend-lib/monitoring/logger/services/logger.service';\nimport { AppConfigurationService } from '~wildo-backend-lib/config/services/app-configuration.backend.service';\nimport { ErrorBuilderBackendService } from '~wildo-backend-lib/errors/services/error-builder.backend.service';\nimport { describeCaughtError, projectCaughtErrorLogFields } from '~wildo-backend-lib/errors/utils/error-log-level-classification.backend.utils';\nimport { defaultDatabaseConfig } from '~wildo-backend-lib/databases/models/databases.backend.schemas';\nimport { ErrorType, ErrorCustomMessageReference } from '@wildo-ai/saas-models';\n\nexport interface RedisHealthCheck {\n status: 'healthy' | 'unhealthy' | 'connecting';\n latency?: number;\n error?: string;\n memory: {\n used: number;\n peak: number;\n };\n}\n\nexport interface CacheOptions {\n ttl?: number; // Time to live in seconds\n organizationId?: string; // For multi-tenant caching\n}\n\nexport interface SessionData {\n userId: string;\n organizationId: string;\n role: string;\n permissions: string[];\n loginTime: number;\n lastActivity: number;\n}\n\n/**\n * Redis Service\n *\n * Handles Redis operations for:\n * - Caching (application data, API responses)\n * - Session management\n * - Real-time data storage\n * - Multi-tenant data isolation\n * - Rate limiting data\n */\n@injectable()\nexport class RedisService {\n private client: RedisClientType | null = null;\n private isConnecting: boolean = false;\n private connectionRetries: number = 0;\n private readonly maxRetries: number = 5;\n private readonly retryDelay: number = 3000; // 3 seconds\n protected logDebug: (message: string, context?: Record<string, unknown>) => void;\n\n /**\n * Canonical app-level Redis key namespace (Slice 3.6).\n *\n * Sourced from `REDIS_KEY_PREFIX` env (originating from\n * `wildo.saas.config.dataServices.redis.keyPrefix`), surfaced via\n * `appConfigService.config.database.redis.keyPrefix`. Every key the\n * service writes becomes `${appPrefix}org:${orgId}:${key}` or\n * `${appPrefix}global:${key}`.\n *\n * REPLACE semantics: this supersedes the legacy\n * `secrets.database.redis.username`-derived prefix (Slice 3.6 audit\n * decision — single source of truth = `wildo.saas.config.dataServices`).\n * The Redis ACL username remains the auth identity but no longer drives\n * key namespacing.\n *\n * When the app does not author `dataServices.redis.keyPrefix`, the\n * runtime computes `<appSlug>:` as the slug-derived default\n * (ACL-alignment fix, 2026-05-26). The historical literal default\n * `'saas:'` was removed because it was global across every\n * un-authored app and DID NOT align with the per-app ACL grant\n * `~<appSlug>:*` — every Redis op returned `NOPERM` until the\n * schema and the ACL agreed on the slug-derived namespace.\n *\n * ACL invariant (enforced in `connect()`): when an authored value\n * is present, it MUST start with `<appSlug>:` so the ACL grant\n * remains a superset of the keys the backend writes.\n */\n private appPrefix: string = '';\n\n constructor(\n @inject(SAAS_SERVICE_TYPES.Logger) private logger: LoggerService,\n @inject(SAAS_SERVICE_TYPES.AppConfigService) private configService: AppConfigurationService,\n @inject(SAAS_SERVICE_TYPES.ErrorBuilderService) private errorBuilder: ErrorBuilderBackendService\n ) {\n this.logDebug = this.logger.debugByType('RedisService');\n this.logger.setDebugTypeLogging('RedisService', false);\n }\n\n /**\n * Compute the canonical app-level Redis key namespace and validate it\n * against the slug-rooted ACL contract. Run as a connection PRECONDITION\n * (before the retry block) so invalid config fails loud, not silently.\n *\n * Source order (Slice 3.6 + ACL-alignment fix 2026-05-26):\n * 1. `config.database.redis.keyPrefix` — the SaaS app's authored value\n * (from `wildo.saas.config.dataServices.redis.keyPrefix`, threaded\n * via the `REDIS_KEY_PREFIX` env). Empty string is preserved as an\n * explicit no-prefix opt-out (tests / no-prefix workloads).\n * 2. `<appSlug>:` — slug-derived default when unauthored. REPLACES the\n * historical literal `'saas:'` default, which was global across every\n * un-authored app and did NOT match the per-app ACL grant\n * `~<appSlug>:*` (apps-manager scoped the ACL to the slug; the\n * backend wrote `saas:<key>` → every op `NOPERM`).\n *\n * Slice 3 deep-review L-9: sourced from `config.database.redis.keyPrefix`\n * directly (NOT the `{ ...defaults, ...config, ...secrets }` merged blob),\n * so a stray `keyPrefix` in the secrets payload can't shadow the\n * non-secret app config.\n *\n * ACL invariant: an authored `keyPrefix` MUST start with `<appSlug>:` so\n * the `~<appSlug>:*` grant covers every key the backend writes.\n */\n private computeAndValidateAppPrefix(): void {\n const config = this.configService.config;\n const appSlug = this.configService.appSlug;\n const slugDerivedDefault = `${appSlug}:`;\n const authoredKeyPrefix = config.database.redis?.keyPrefix;\n this.appPrefix = authoredKeyPrefix !== undefined\n ? authoredKeyPrefix\n : slugDerivedDefault;\n\n if (this.appPrefix && !this.appPrefix.startsWith(`${appSlug}:`)) {\n throw this.errorBuilder.buildError(\n ErrorType.CONFIGURATION,\n undefined,\n {\n customMessageReference: ErrorCustomMessageReference.CONFIGURATION,\n context: {\n message:\n `dataServices.redis.keyPrefix '${this.appPrefix}' must start with '${appSlug}:' `\n + 'so the per-app ACL grant `~<appSlug>:*` covers it. '\n + `Either omit the field (default '${slugDerivedDefault}' applies) `\n + `or change the authored value to '${slugDerivedDefault}<suffix>'.`,\n authoredKeyPrefix: this.appPrefix,\n appSlug,\n },\n },\n );\n }\n }\n\n /**\n * Connect to Redis\n */\n public async connect(): Promise<RedisClientType> {\n if (this.client && this.client.isReady) {\n return this.client;\n }\n\n if (this.isConnecting) {\n // Wait for existing connection attempt\n return new Promise((resolve, reject) => {\n const checkConnection = () => {\n if (this.client && this.client.isReady) {\n resolve(this.client);\n } else if (!this.isConnecting) {\n reject(new Error('Connection attempt failed'));\n } else {\n setTimeout(checkConnection, 100);\n }\n };\n checkConnection();\n });\n }\n\n this.isConnecting = true;\n\n // Compute + validate the app-level Redis key namespace as a CONFIG\n // PRECONDITION — BEFORE the connection retry block. A keyPrefix that\n // doesn't satisfy the slug-rooted ACL contract is invalid config, not a\n // transient connection failure: it must throw immediately (with its\n // actionable message) and NOT be swallowed by the catch's retry loop.\n // (Once the client connects once and `isReady` flips true, a retry\n // would re-enter connect(), hit the `client.isReady` early-return, and\n // resolve — masking the invalid config.)\n this.computeAndValidateAppPrefix();\n\n try {\n const config = this.configService.config;\n const secrets = this.configService.secrets;\n const redisDefaultConfig = defaultDatabaseConfig.redis;\n\n const redisConfig = {\n ...redisDefaultConfig,\n ...config.database.redis,\n ...secrets.database.redis\n };\n\n if (!redisConfig) {\n const error = this.errorBuilder.buildError(\n ErrorType.CONFIGURATION,\n undefined,\n { customMessageReference: ErrorCustomMessageReference.REDIS_INITIALIZATION_FAILED }\n );\n throw error;\n }\n\n const redisUrl = `redis://${redisConfig.host}:${redisConfig.port}`;\n\n this.logDebug('Connecting to Redis', {\n url: this.maskConnectionString(redisUrl)\n });\n\n // Redis options for SaaS multi-tenant architecture\n // Include username for ACL authentication (required when using app-specific credentials)\n const redisOptions: RedisClientOptions = {\n socket: {\n host: redisConfig.host,\n port: redisConfig.port,\n connectTimeout: redisConfig.connectTimeout || 10000,\n // `connect()` already owns a finite five-attempt policy below. If\n // node-redis also reconnects internally, its first promise can stay\n // pending forever and the service-owned retry budget never advances.\n reconnectStrategy: false,\n },\n username: redisConfig.username,\n password: redisConfig.password,\n database: redisConfig.db\n };\n\n this.client = createClient(redisOptions) as RedisClientType;\n\n // Setup event handlers\n this.setupEventHandlers(this.client, 'main');\n\n // Wait for connection to be ready\n await this.client.connect();\n\n // `this.appPrefix` was already computed + validated as a config\n // precondition before the retry block (see\n // `computeAndValidateAppPrefix`). It is intentionally NOT recomputed\n // here: doing so post-connect put the validation inside the retry\n // catch, where — once the client connected and `isReady` flipped true\n // — a retry would hit the `client.isReady` early-return at the top of\n // connect() and resolve, masking an invalid keyPrefix. Config\n // validation belongs before the connection, not after it.\n\n this.connectionRetries = 0;\n this.isConnecting = false;\n\n this.logDebug('Redis connected successfully', {\n isReady: this.client.isReady,\n isOpen: this.client.isOpen,\n appPrefix: this.appPrefix || '(none)'\n });\n\n return this.client;\n\n } catch (error) {\n this.isConnecting = false;\n this.connectionRetries++;\n\n this.logger.error('Redis connection failed', {\n ...projectCaughtErrorLogFields(error),\n retries: this.connectionRetries,\n maxRetries: this.maxRetries\n });\n\n if (this.connectionRetries < this.maxRetries) {\n this.logDebug(`Retrying Redis connection in ${this.retryDelay}ms...`);\n await this.delay(this.retryDelay);\n return await this.connect();\n } else {\n const connectionError = this.errorBuilder.buildError(\n ErrorType.DATABASE,\n undefined,\n {\n customMessageReference: ErrorCustomMessageReference.REDIS_CONNECTION_FAILED,\n context: {\n retries: this.connectionRetries,\n maxRetries: this.maxRetries,\n originalError: describeCaughtError(error)\n }\n }\n );\n throw connectionError;\n }\n }\n }\n\n /**\n * Disconnect from Redis\n */\n public async disconnect(): Promise<void> {\n this.logDebug('Disconnecting from Redis');\n\n if (this.client) {\n await this.client.quit();\n this.client = null;\n }\n\n this.logDebug('Redis disconnected successfully');\n }\n\n /**\n * Get Redis client\n *\n * Returns the client OBJECT, which may exist without being usable — see\n * {@link getReadyClient}, which is what almost every caller actually wants.\n */\n public getClient(): RedisClientType | null {\n return this.client;\n }\n\n /**\n * The Redis client, but only while it can actually serve a command.\n *\n * ## Why this exists as its own accessor\n *\n * `getClient()` answers the client OBJECT. A client that was constructed but never connected — the\n * normal state wherever Redis is not configured — is TRUTHY, so `if (client)` reads as \"Redis is\n * available\" and the next command throws `The client is closed`.\n *\n * That is not hypothetical and it was not one site. Every caller in this package guarded on\n * truthiness alone, so on the dev companion — which logs `Redis not properly configured … using\n * memory adapter` at boot — the WebSocket disconnect handler warned on EVERY socket close, filling\n * the warning channel with a condition nobody can act on (issue #242). The same shape sat in the\n * A2A cancellation service, where each site ALREADY had a considered fallback for a null client and\n * simply never reached it.\n *\n * Returning `null` for a not-ready client means every one of those existing fallbacks — \"cross-pod\n * cancellation is disabled\", \"fail safe toward alive\", \"the lease mechanism is not writable\" — now\n * fires for an unusable client exactly as it does for an absent one, which is what each of them\n * already meant.\n *\n * A caller that genuinely wants the object regardless of state keeps {@link getClient}.\n */\n public getReadyClient(): RedisClientType | null {\n return this.client?.isReady ? this.client : null;\n }\n\n /**\n * Get the canonical app-level Redis key namespace prefix (Slice 3.6\n * + 2026-05-26 ACL-alignment fix).\n *\n * Returns the value of `wildo.saas.config.dataServices.redis.keyPrefix`\n * (e.g., `\"wonder-todos:\"`), surfaced via the `REDIS_KEY_PREFIX` env\n * variable into `config.database.redis.keyPrefix`. When the field is\n * not authored, falls back to `<appSlug>:` (e.g.,\n * `\"wonder-todos:\"` for app `wonder-todos`) — the slug-derived\n * default that aligns with the per-app ACL grant `~<appSlug>:*`.\n *\n * **Slice 3 deep-review L-9 (2026-04-13) — sourcing precedence.** The\n * authoritative source is `config.database.redis.keyPrefix`, NOT the\n * merged `redisConfig.keyPrefix` blob constructed in `connect()`. The\n * connection-options blob spreads `secrets.database.redis` on top of\n * `config.database.redis`, which would let a stray `keyPrefix` in the\n * secrets payload silently shadow the authored config value. The\n * namespace prefix is non-secret authored config, so `connect()`\n * intentionally reads it directly from `config.database.redis?.keyPrefix\n * ?? \\`${configService.appSlug}:\\`` and stores it on `this.appPrefix`\n * before the merged blob is built. Pinned by the L-9 regression test\n * in `redis.service.test.ts`.\n *\n * Used by `WebSocketService` and any other service that must construct\n * Redis keys outside `set/get/del` (e.g., `eval()`, pub/sub channel\n * names, scan patterns) so they share the same namespace as\n * `createTenantKey()`.\n */\n public getAppPrefix(): string {\n return this.appPrefix;\n }\n\n /**\n * Check Redis health\n */\n public async healthCheck(): Promise<RedisHealthCheck> {\n try {\n if (!this.client || !this.client.isReady) {\n return {\n status: 'unhealthy',\n error: 'No active connection',\n memory: { used: 0, peak: 0 }\n };\n }\n\n const startTime = Date.now();\n\n // Perform ping operation\n await this.client.ping();\n\n const latency = Date.now() - startTime;\n\n // Get memory info\n const info = await this.client.info('memory');\n const memoryUsed = this.parseInfoValue(info, 'used_memory');\n const memoryPeak = this.parseInfoValue(info, 'used_memory_peak');\n\n return {\n status: 'healthy',\n latency,\n memory: {\n used: memoryUsed,\n peak: memoryPeak\n }\n };\n\n } catch (error) {\n this.logger.error('Redis health check failed', {\n ...projectCaughtErrorLogFields(error)\n });\n\n return {\n status: 'unhealthy',\n error: describeCaughtError(error),\n memory: { used: 0, peak: 0 }\n };\n }\n }\n\n /**\n * Cache operations with multi-tenant support\n */\n public async set(\n key: string,\n value: any,\n options: CacheOptions = {}\n ): Promise<void> {\n if (!this.client) {\n const error = this.errorBuilder.buildError(\n ErrorType.DATABASE,\n undefined,\n { customMessageReference: ErrorCustomMessageReference.REDIS_CONNECTION_FAILED }\n );\n throw error;\n }\n\n const tenantKey = this.createTenantKey(key, options.organizationId);\n const serializedValue = JSON.stringify(value);\n\n if (options.ttl) {\n await this.client.setEx(tenantKey, options.ttl, serializedValue);\n } else {\n await this.client.set(tenantKey, serializedValue);\n }\n\n this.logDebug('Cache set', { key: tenantKey, ttl: options.ttl });\n }\n\n public async get<T = any>(\n key: string,\n organizationId?: string\n ): Promise<T | null> {\n if (!this.client) {\n return null;\n }\n\n const tenantKey = this.createTenantKey(key, organizationId);\n const value = await this.client.get(tenantKey);\n\n if (value === null) {\n return null;\n }\n\n try {\n return JSON.parse(value as string) as T;\n } catch (error) {\n this.logger.error('Failed to parse cached value', {\n key: tenantKey,\n ...projectCaughtErrorLogFields(error)\n });\n return null;\n }\n }\n\n /**\n * Atomically read a value and delete it in one round-trip (Redis native\n * `GETDEL`, available since Redis 6.2).\n *\n * Symmetric with {@link set} / {@link get}: applies the SAME multi-tenant\n * key scoping AND the SAME `JSON.parse` deserialization, so a value written\n * with `set()` round-trips correctly. Reach for this on single-use tokens —\n * WebAuthn challenges, one-time codes — where the read must consume the\n * value atomically (no get-then-del race, no replay of a still-present\n * token).\n *\n * Do NOT hand-roll this with `eval()` + a raw `GET`/`DEL` Lua script: the\n * raw `eval` path returns the stored bytes verbatim and bypasses the\n * `JSON.parse` that `set()`'s `JSON.stringify` requires, so the caller gets\n * a value wrapped in literal quote characters (`\"\\\"abc\\\"\"` instead of\n * `abc`). That asymmetry silently broke passkey enrollment until this\n * method centralized the contract.\n *\n * Returns `null` if the key is absent (or the stored value can't be parsed).\n */\n public async getDel<T = any>(\n key: string,\n organizationId?: string\n ): Promise<T | null> {\n if (!this.client) {\n return null;\n }\n\n const tenantKey = this.createTenantKey(key, organizationId);\n const value = await this.client.getDel(tenantKey);\n\n if (value === null) {\n return null;\n }\n\n try {\n return JSON.parse(value as string) as T;\n } catch (error) {\n this.logger.error('Failed to parse cached value', {\n key: tenantKey,\n ...projectCaughtErrorLogFields(error)\n });\n return null;\n }\n }\n\n public async del(key: string, organizationId?: string): Promise<void> {\n if (!this.client) {\n const error = this.errorBuilder.buildError(\n ErrorType.DATABASE,\n undefined,\n { customMessageReference: ErrorCustomMessageReference.REDIS_CONNECTION_FAILED }\n );\n throw error;\n }\n\n const tenantKey = this.createTenantKey(key, organizationId);\n await this.client.del(tenantKey);\n\n this.logDebug('Cache deleted', { key: tenantKey });\n }\n\n public async exists(key: string, organizationId?: string): Promise<boolean> {\n if (!this.client) {\n return false;\n }\n\n const tenantKey = this.createTenantKey(key, organizationId);\n const result = await this.client.exists(tenantKey);\n return result === 1;\n }\n\n /**\n * Session management\n */\n public async setSession(\n sessionId: string,\n sessionData: SessionData,\n ttl: number = 86400\n ): Promise<void> {\n const key = `session:${sessionId}`;\n await this.set(key, sessionData, { ttl });\n }\n\n public async getSession(sessionId: string): Promise<SessionData | null> {\n const key = `session:${sessionId}`;\n return await this.get<SessionData>(key);\n }\n\n public async deleteSession(sessionId: string): Promise<void> {\n const key = `session:${sessionId}`;\n await this.del(key);\n }\n\n public async updateSessionActivity(sessionId: string): Promise<void> {\n const session = await this.getSession(sessionId);\n if (session) {\n session.lastActivity = Date.now();\n await this.setSession(sessionId, session);\n }\n }\n\n /**\n * Build a fully-qualified Redis key with app prefix and tenant scope.\n * Use this when constructing keys for `eval()` Lua scripts so they match\n * the keys produced by `set()`/`get()`/`del()`.\n */\n public buildKey(key: string, organizationId?: string): string {\n return this.createTenantKey(key, organizationId);\n }\n\n /**\n * Execute a Lua script atomically on the Redis server.\n * Used for atomic read-modify-write operations (e.g., session updates with version checks).\n *\n * IMPORTANT: Keys passed here must be fully-qualified (use `buildKey()` to construct them).\n * Unlike `set()`/`get()`, this method does NOT apply `createTenantKey()` automatically.\n */\n public async eval(script: string, keys: string[], args: string[]): Promise<any> {\n if (!this.client) {\n const error = this.errorBuilder.buildError(\n ErrorType.DATABASE,\n undefined,\n { customMessageReference: ErrorCustomMessageReference.REDIS_CONNECTION_FAILED }\n );\n throw error;\n }\n return this.client.eval(script, { keys, arguments: args });\n }\n\n /**\n * Rate limiting support\n */\n public async incrementRateLimit(\n identifier: string,\n windowSeconds: number,\n organizationId?: string\n ): Promise<number> {\n if (!this.client) {\n const error = this.errorBuilder.buildError(\n ErrorType.DATABASE,\n undefined,\n { customMessageReference: ErrorCustomMessageReference.REDIS_CONNECTION_FAILED }\n );\n throw error;\n }\n\n const key = this.createTenantKey(`rate_limit:${identifier}`, organizationId);\n\n const current = await this.client.eval(\n `\n local current = redis.call('INCR', KEYS[1])\n local ttl = redis.call('TTL', KEYS[1])\n if ttl < 0 then\n redis.call('EXPIRE', KEYS[1], tonumber(ARGV[1]))\n end\n return current\n `,\n {\n keys: [key],\n arguments: [String(windowSeconds)],\n },\n );\n return typeof current === 'number' ? current : Number(current);\n }\n\n /**\n * Get the current rate limit count for a given identifier without incrementing.\n * Uses the same key format as incrementRateLimit() — callers should NOT manually\n * prepend \"rate_limit:\" since this method does it internally.\n */\n public async getRateLimitCount(\n identifier: string,\n organizationId?: string,\n ): Promise<number> {\n if (!this.client) {\n const error = this.errorBuilder.buildError(\n ErrorType.DATABASE,\n undefined,\n { customMessageReference: ErrorCustomMessageReference.REDIS_CONNECTION_FAILED }\n );\n throw error;\n }\n\n const key = this.createTenantKey(`rate_limit:${identifier}`, organizationId);\n const value = await this.client.get(key);\n if (value === null || typeof value !== 'string') return 0;\n\n const parsed = parseInt(value, 10);\n return isNaN(parsed) ? 0 : parsed;\n }\n\n public async getRateLimitTtl(\n identifier: string,\n organizationId?: string,\n ): Promise<number> {\n if (!this.client) {\n const error = this.errorBuilder.buildError(\n ErrorType.DATABASE,\n undefined,\n { customMessageReference: ErrorCustomMessageReference.REDIS_CONNECTION_FAILED }\n );\n throw error;\n }\n\n const key = this.createTenantKey(`rate_limit:${identifier}`, organizationId);\n const ttl = await this.client.ttl(key);\n return ttl > 0 ? ttl : 0;\n }\n\n /**\n * Create tenant-scoped key with app prefix for ACL isolation.\n *\n * Key format: {appPrefix}{scope}:{key}\n * - appPrefix: canonical app namespace from\n * `wildo.saas.config.dataServices.redis.keyPrefix` (Slice 3.6),\n * surfaced via `REDIS_KEY_PREFIX` env (e.g., `\"wonder-todos:\"`)\n * - scope: \"org:{orgId}\" for org-scoped data, \"global\" for app-global data\n * - key: the actual key name\n *\n * Examples:\n * - \"wonder-todos:org:abc123:cache:users\"\n * - \"wonder-todos:global:session:xyz789\"\n */\n private createTenantKey(key: string, organizationId?: string): string {\n if (organizationId) {\n return `${this.appPrefix}org:${organizationId}:${key}`;\n }\n return `${this.appPrefix}global:${key}`;\n }\n\n /**\n * Setup Redis event handlers\n */\n private setupEventHandlers(client: RedisClientType, clientType: string): void {\n client.on('connect', () => {\n this.logDebug(`Redis ${clientType} client connecting`);\n });\n\n client.on('ready', () => {\n this.logDebug(`Redis ${clientType} client ready`);\n });\n\n client.on('error', (error) => {\n this.logger.error(`Redis ${clientType} client error`, {\n error: error.message\n });\n });\n\n client.on('close', () => {\n this.logger.warn(`Redis ${clientType} client connection closed`);\n });\n\n client.on('reconnecting', () => {\n this.logDebug(`Redis ${clientType} client reconnecting`);\n });\n }\n\n /**\n * Parse value from Redis INFO command output\n */\n private parseInfoValue(info: string, key: string): number {\n const regex = new RegExp(`${key}:(\\\\d+)`);\n const match = info.match(regex);\n return match ? parseInt(match[1]) : 0;\n }\n\n /**\n * Mask sensitive parts of connection string for logging\n */\n private maskConnectionString(url: string): string {\n try {\n const redisUrl = new URL(url);\n if (redisUrl.password) {\n redisUrl.password = '***';\n }\n return redisUrl.toString();\n } catch {\n return url.replace(/:([^@]+)@/, ':***@');\n }\n }\n\n /**\n * Delay utility for connection retries\n */\n private delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-handler.backend.service.d.ts","sourceRoot":"","sources":["../../../../../src/errors/services/error-handler.backend.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE1D,OAAO,EAAE,aAAa,EAAE,MAAM,8DAA8D,CAAC;AAE7F,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAK7E,OAAO,EAIL,kBAAkB,EAInB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,gBAAgB,EAAG,MAAM,qEAAqE,CAAC;AAExG;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oCAAoC,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAE1E;AAED;;;GAGG;AACH,qBACa,mBAAmB;IAIO,OAAO,CAAC,MAAM;IACD,OAAO,CAAC,YAAY;IAJtE,SAAS,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;gBAGpC,MAAM,EAAE,aAAa,EACR,YAAY,EAAE,0BAA0B;IAMlG;;;OAGG;IACH,mBAAmB,IAAI,CAAC,KAAK,EAAE,KAAK,GAAC,kBAAkB,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI;IAyCjH,0BAA0B,CAAC,KAAK,EAAE,KAAK,GAAC,kBAAkB,EAAE,gBAAgB,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,GAAG,IAAI;IAW3G,OAAO,CAAC,iBAAiB;
|
|
1
|
+
{"version":3,"file":"error-handler.backend.service.d.ts","sourceRoot":"","sources":["../../../../../src/errors/services/error-handler.backend.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE1D,OAAO,EAAE,aAAa,EAAE,MAAM,8DAA8D,CAAC;AAE7F,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAK7E,OAAO,EAIL,kBAAkB,EAInB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,gBAAgB,EAAG,MAAM,qEAAqE,CAAC;AAExG;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oCAAoC,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAE1E;AAED;;;GAGG;AACH,qBACa,mBAAmB;IAIO,OAAO,CAAC,MAAM;IACD,OAAO,CAAC,YAAY;IAJtE,SAAS,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;gBAGpC,MAAM,EAAE,aAAa,EACR,YAAY,EAAE,0BAA0B;IAMlG;;;OAGG;IACH,mBAAmB,IAAI,CAAC,KAAK,EAAE,KAAK,GAAC,kBAAkB,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI;IAyCjH,0BAA0B,CAAC,KAAK,EAAE,KAAK,GAAC,kBAAkB,EAAE,gBAAgB,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,GAAG,IAAI;IAW3G,OAAO,CAAC,iBAAiB;IA4GzB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAY1B;;OAEG;IACH,YAAY,CAAC,EAAE,EAAE,QAAQ,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,YAAY,KAAK,IAAI;IAMrF;;OAEG;IACH,wBAAwB,IAAI,IAAI;IAiBhC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmDG;IACH,uBAAuB,IAAI,IAAI;IAsB/B;;OAEG;IACH,wBAAwB,IAAI,IAAI;CAKjC"}
|
|
@@ -164,6 +164,11 @@ let ErrorHandlerService = class ErrorHandlerService {
|
|
|
164
164
|
customMessage: saasError.customMessageReference,
|
|
165
165
|
...(saasError.customMessageReference && { customMessageReference: saasError.customMessageReference }),
|
|
166
166
|
operationPath: saasError.operationPathKey,
|
|
167
|
+
// A stable, operation-specific refusal code (e.g. `LAST_ORGANIZATION_OWNER`) is the
|
|
168
|
+
// contract clients branch on. It is authored in `context.code` and allow-listed by the
|
|
169
|
+
// sanitizer, so it also appears under `details.code`; hoisting it to the top-level field the
|
|
170
|
+
// schema reserves is what lets the customer documentation say "branch on `error.code`".
|
|
171
|
+
...(typeof sanitizedError.context?.code === 'string' && { code: sanitizedError.context.code }),
|
|
167
172
|
// Phase 3 (SD5) — a safe, non-PII support/trace id the user can quote; joins this response to
|
|
168
173
|
// the backend logs + audit for the same request. The error's captured id, else the ambient frame.
|
|
169
174
|
correlationId: saasError.correlationId ?? getCurrentCorrelationId(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-handler.backend.service.js","sourceRoot":"","sources":["../../../../../src/errors/services/error-handler.backend.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { NextFunction, Request, Response } from 'express';\nimport { inject, injectable } from 'inversify';\nimport { LoggerService } from '~wildo-backend-lib/monitoring/logger/services/logger.service';\nimport { SAAS_SERVICE_TYPES } from '~wildo-backend-lib/inject/models/service-types';\nimport { ErrorBuilderBackendService } from './error-builder.backend.service';\nimport { logWildoBackendErrorAtClassifiedSeverity, describeCaughtError } from '~wildo-backend-lib/errors/utils/error-log-level-classification.backend.utils';\nimport { getCurrentCorrelationId } from '~wildo-backend-lib/runtime/models/request-context.als.backend';\n\n// Import the unified error system\nimport {\n ErrorResponse,\n ErrorType,\n ExecutionContext_InitiatorIds,\n WildoError_Backend,\n ValidationErrors,\n ValidationErrorEntry,\n WildoHeaderKeys,\n} from '@wildo-ai/saas-models';\nimport { ExecutionContext, } from '~wildo-backend-lib/runtime/models/execution-context.backend.schemas';\n\n/**\n * Narrowly isolates uncaught exceptions that originate inside\n * `@opentelemetry/sdk-logs`'s background batch-flush timer from every other\n * uncaught exception. Matched ONLY by the presence of the `sdk-logs` package\n * segment in the stack trace — this is deliberately conservative: any error\n * whose stack does not clearly point into that package still hard-exits via\n * the default path, so a real host-state-corrupting bug is never masked.\n *\n * Root cause (2026-08-06): the timer callback in\n * `BatchLogRecordProcessorBase` does `this._flushOneBatch().then(...).catch(...)`\n * on a fixed interval, independent of any request. Observed in production dev\n * use as `TypeError: this._flushOneBatch(...).then(...).catch is not a\n * function`. The processor's own promise chain broke down before its internal\n * `globalErrorHandler` safety net could catch it, so the failure escaped as a\n * process-level uncaught exception — killing an otherwise-healthy host over a\n * background telemetry-export housekeeping tick.\n */\nexport function isNonFatalTelemetryPipelineException(error: Error): boolean {\n return typeof error.stack === 'string' && error.stack.includes('@opentelemetry/sdk-logs');\n}\n\n/**\n * Unified Error Handler Service\n * Handles all errors using the new unified error management system\n */\n@injectable()\nexport class ErrorHandlerService {\n protected logDebug: (message: string, context?: Record<string, unknown>) => void;\n\n constructor(\n @inject(SAAS_SERVICE_TYPES.Logger) private logger: LoggerService,\n @inject(SAAS_SERVICE_TYPES.ErrorBuilderService) private errorBuilder: ErrorBuilderBackendService\n ) {\n this.logDebug = this.logger.debugByType('ErrorHandlerService');\n this.logger.setDebugTypeLogging('ErrorHandlerService', false);\n }\n\n /**\n * Express error handling middleware with unified error system\n * Express requires exactly 4 parameters: (err, req, res, next)\n */\n expressErrorHandler(): (error: Error|WildoError_Backend, req: Request, res: Response, next: NextFunction) => void {\n return (error: Error|WildoError_Backend, req: Request, res: Response, next: NextFunction): void => {\n // Check if response has already been sent\n if (res.headersSent) {\n // If headers are already sent, delegate to Express default handler\n return next(error);\n }\n\n // Extract executionContext from request if available\n const executionContext = (req as any).executionContext as ExecutionContext<any> | undefined;\n\n const errorToHandle = error instanceof Error\n ? this.errorBuilder.buildFromStandardError(error, executionContext)\n : error;\n errorToHandle.ipAddress = this.getClientIpAddress(req);\n\n // Single, central log for every HTTP error — emitted at the *classified*\n // operational severity rather than a blanket `error`. This is the one\n // true HTTP error boundary (mounted globally in http-server and per\n // sub-router), so logging here guarantees exactly one line per failed\n // request: expected client conditions (e.g. an expired token the client\n // will refresh) stay at `debug`, genuine server faults stay at `error`\n // with a stack. Per-controller boundaries no longer log — they forward\n // here — which removes the historic double-logging and the noise flood.\n logWildoBackendErrorAtClassifiedSeverity(\n this.logger,\n 'HTTP request failed',\n errorToHandle,\n // Boundary-specific extras only — the error's own fields (errorType,\n // statusCode, customMessageReference, operationPath, …) are projected\n // by the helper.\n {\n method: req.method,\n path: req.path,\n },\n );\n\n this.sendErrorResponse(errorToHandle, res);\n };\n }\n\n handleBatchesCronjobsError(error: Error|WildoError_Backend, executionContext?: ExecutionContext<any>): void {\n const errorToHandle = error instanceof Error ? this.errorBuilder.buildFromStandardError(error, executionContext) : error;\n\n // Batch/cron is a non-HTTP boundary (no `expressErrorHandler` downstream),\n // so this is where the line must be emitted — at the classified severity.\n // The error's own fields are projected by the helper; the stack is attached\n // only when the level is `error`.\n logWildoBackendErrorAtClassifiedSeverity(this.logger, 'Batch/cronjob execution failed', errorToHandle);\n }\n\n\n private sendErrorResponse(saasError: WildoError_Backend, res: Response, initiatorIds?: ExecutionContext_InitiatorIds): void {\n // Double-check if headers are already sent\n if (res.headersSent) {\n this.logger.warn('Cannot send error response - headers already sent', {\n errorType: saasError.type,\n statusCode: saasError.statusCode\n });\n return;\n }\n\n try {\n // Build validationErrors for responses that carry field-level error information.\n // Values can be plain strings (Zod errors) or structured ValidationErrorEntry objects (unique constraints).\n //\n // C3: Intentional extraction order — validationErrors are extracted from the raw error\n // BEFORE sanitizeErrorForClient() runs. This is by design: zodIssues contain field-level\n // validation messages that must appear in the response for form UX, even though the\n // sanitization layer may strip them from `details`. If zodIssues are removed from the\n // sanitization allowlist in the future, validationErrors will still be included because\n // they are extracted independently above the sanitization call.\n let validationErrors: ValidationErrors | undefined;\n\n // CONFLICT (409): Unique constraint violations — use structured i18n entries\n if (saasError.type === ErrorType.CONFLICT && saasError.context?.fieldName) {\n const fieldName = saasError.context.fieldName as string;\n const duplicateValue = saasError.context.duplicateValue as string | undefined;\n const customErrorMessage = saasError.context.customErrorMessage as string | undefined;\n const fallbackMessage = customErrorMessage\n ? customErrorMessage.replace('{fieldName}', fieldName)\n : duplicateValue\n ? `\"${duplicateValue}\" already exists`\n : 'Value already exists';\n\n const entry: ValidationErrorEntry = {\n key: 'validation.unique.conflict',\n message: fallbackMessage,\n params: {\n fieldName,\n ...(duplicateValue ? { value: duplicateValue } : {}),\n },\n };\n validationErrors = { [fieldName]: entry };\n }\n\n // VALIDATION (400): Zod validation errors — plain strings (already localized by Zod)\n if (saasError.type === ErrorType.VALIDATION && saasError.context?.zodIssues) {\n const zodIssues = saasError.context.zodIssues as Array<{ path: (string | number)[]; message: string }>;\n validationErrors = zodIssues.reduce((acc, issue) => {\n const fieldPath = issue.path.join('.');\n acc[fieldPath] = issue.message;\n return acc;\n }, {} as ValidationErrors);\n }\n\n // H9 fix: Sanitize error at serialization boundary — backend logs retain full context,\n // but the HTTP response strips internal details (emails, stack traces, session IDs, etc.)\n const sanitizedError = this.errorBuilder.sanitizeErrorForClient(saasError);\n const retryAfterSeconds = sanitizedError.context?.retryAfterSeconds;\n if (\n (typeof retryAfterSeconds === 'number' || typeof retryAfterSeconds === 'string')\n && retryAfterSeconds !== ''\n && typeof res.getHeader === 'function'\n && typeof res.setHeader === 'function'\n && res.getHeader('Retry-After') === undefined\n ) {\n res.setHeader('Retry-After', String(retryAfterSeconds));\n }\n\n const response: ErrorResponse = {\n error: {\n type: saasError.type,\n severity: saasError.severity,\n message: saasError.customMessageReference,\n customMessage: saasError.customMessageReference,\n ...(saasError.customMessageReference && { customMessageReference: saasError.customMessageReference }),\n operationPath: saasError.operationPathKey,\n // Phase 3 (SD5) — a safe, non-PII support/trace id the user can quote; joins this response to\n // the backend logs + audit for the same request. The error's captured id, else the ambient frame.\n correlationId: saasError.correlationId ?? getCurrentCorrelationId(),\n timestamp: saasError.timestamp.toISOString(),\n initiatorIds: saasError.initiatorIds,\n // executionType: saasError.executionType,\n // stack: saasError.stack,\n ...(sanitizedError.context && { details: sanitizedError.context }),\n // FIX-P6-H1: Include validationErrors at top level for frontend parsing\n ...(validationErrors && { validationErrors }),\n // Phase 1b: Include user guidance if present on the error\n ...(saasError.userGuidance && { userGuidance: saasError.userGuidance }),\n }\n };\n\n res.status(saasError.statusCode).json(response);\n } catch (sendError) {\n // If sending the error response fails, log it but don't throw\n // This prevents Express default HTML error handler from being triggered\n this.logger.error('Failed to send error response', {\n originalError: saasError.type,\n sendError: describeCaughtError(sendError)\n });\n }\n }\n\n\n /**\n * Get client IP address\n */\n private getClientIpAddress(req: Request): string {\n const xForwardedFor = req.headers[WildoHeaderKeys.FORWARDED_FOR];\n if (xForwardedFor) {\n return Array.isArray(xForwardedFor) ? xForwardedFor[0] : xForwardedFor.split(',')[0];\n }\n\n return req.headers[WildoHeaderKeys.REAL_IP] as string ||\n req.connection?.remoteAddress ||\n req.socket?.remoteAddress ||\n 'unknown';\n }\n\n /**\n * Create async error wrapper for route handlers\n */\n asyncHandler(fn: Function): (req: Request, res: Response, next: NextFunction) => void {\n return (req: Request, res: Response, next: NextFunction): void => {\n Promise.resolve(fn(req, res, next)).catch(next);\n };\n }\n\n /**\n * Global unhandled rejection handler\n */\n handleUnhandledRejection(): void {\n process.on('unhandledRejection', (reason: any, promise: Promise<any>) => {\n const errorToHandle = this.errorBuilder.buildError(ErrorType.UNHANDLED_REJECTION, undefined, {\n stack: reason?.stack,\n context: {\n name: reason?.name,\n message: reason?.message,\n promise: promise.toString()\n }\n });\n this.logger.fatal('Unhandled Promise Rejection', { ...errorToHandle });\n\n // Graceful shutdown\n process.exit(1);\n });\n }\n\n /**\n * Global uncaught exception handler.\n *\n * Exits the process for every uncaught exception EXCEPT the narrow class\n * isolated by {@link isNonFatalTelemetryPipelineException}. Node's own\n * guidance (\"uncaught exception means unknown state, exit\") assumes the\n * failure came from the host's own request/business logic. A background\n * telemetry batch-flush timer (OTel's `BatchLogRecordProcessor`, ticking\n * every few seconds independently of any request) is a different failure\n * domain: its own state corruption says nothing about whether the host's\n * actual work is safe to continue, and `otel-preload.ts` already commits to\n * \"telemetry failures must never affect host availability\" for SDK-start\n * failures (Audit A-17) — this extends the same policy to a *runtime*\n * telemetry failure. Every other uncaught exception keeps today's hard-exit\n * behavior unchanged.\n *\n * NOTE: this method is not wired into any production entrypoint today (no\n * caller reaches `initializeGlobalHandlers`/`handleUncaughtException`) —\n * kept for any future/direct caller, and as the tested home of the shared\n * {@link isNonFatalTelemetryPipelineException} predicate. It cannot simply\n * become \"the\" global handler: it depends on DI (`ErrorBuilderBackendService`\n * via the container), so it is only constructible AFTER\n * `setupSaasBackendInjection()` — every real crash boundary below registers\n * a RAW `process.on(...)` listener at module load, before the container\n * exists, specifically so container-construction failures and early-startup\n * throws are still caught. The listeners that actually apply this predicate\n * in production, all against the SAME exported function:\n *\n * - `runPlatformServiceEntrypoint` (platform\n * `platform-shared-lib/src/core/startup/platform-service-process-boundary.ts`)\n * — protects `platform-apps-manager`, `platform-crontabs-batches-manager`,\n * `platform-module-registry`. This is the process that actually crashed\n * on 2026-08-06, on `this._flushOneBatch(...).then(...).catch is not a\n * function` inside `@opentelemetry/sdk-logs`'s batch timer.\n * - `WorkerShutdownManagerBackendService.registerSignalHandlers` (this\n * package, `queue/worker-shutdown-manager.backend.service.ts`) — the\n * engine-shared listener reached by every NON-PLATFORM host\n * (backend-api, workers, minions) that runs a queue worker OR has\n * telemetry active, via `StartApplicationBackendService`'s\n * `!isPlatformApplication`/`!isPlatformMode`-gated calls into\n * `ObservabilityBackendService.initialize`/`QueueWorkerInitializer.initialize`.\n * Platform hosts skip both gates entirely and are NOT covered by this\n * listener — they rely solely on the `runPlatformServiceEntrypoint` entry\n * above.\n * - The hand-rolled bootstrap `process.on('uncaughtException'/'unhandledRejection', ...)`\n * blocks in each application entrypoint (Wonder Todos:\n * `backend-api/src/index.ts`, `minions/marketing-scrapper/src/index.ts`,\n * `workers/my-node-worker/src/index.ts`) — these are hand-authored\n * per-app boilerplate, not generated from a shared template, so a future\n * app scaffolded the same way needs the same carve-out applied by hand\n * until this pattern is consolidated into a shared bootstrap helper.\n */\n handleUncaughtException(): void {\n process.on('uncaughtException', (error: Error) => {\n const errorToHandle = this.errorBuilder.buildError(ErrorType.UNCAUGHT_EXCEPTION, undefined, {\n stack: error.stack,\n context: {\n name: error.name,\n message: error.message,\n }\n });\n\n if (isNonFatalTelemetryPipelineException(error)) {\n this.logger.error('Uncaught exception isolated to the telemetry pipeline; host stays up', { ...errorToHandle });\n return;\n }\n\n this.logger.fatal('Uncaught Exception', { ...errorToHandle });\n\n // Graceful shutdown\n process.exit(1);\n });\n }\n\n /**\n * Initialize global error handlers\n */\n initializeGlobalHandlers(): void {\n this.handleUnhandledRejection();\n this.handleUncaughtException();\n }\n\n}\n"]}
|
|
1
|
+
{"version":3,"file":"error-handler.backend.service.js","sourceRoot":"","sources":["../../../../../src/errors/services/error-handler.backend.service.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,8DAA8D,CAAC;AAC7F,OAAO,EAAE,kBAAkB,EAAE,MAAM,gDAAgD,CAAC;AACpF,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAC7E,OAAO,EAAE,wCAAwC,EAAE,mBAAmB,EAAE,MAAM,8EAA8E,CAAC;AAC7J,OAAO,EAAE,uBAAuB,EAAE,MAAM,+DAA+D,CAAC;AAExG,kCAAkC;AAClC,OAAO,EAEL,SAAS,EAKT,eAAe,GAChB,MAAM,uBAAuB,CAAC;AAG/B;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,oCAAoC,CAAC,KAAY;IAC/D,OAAO,OAAO,KAAK,CAAC,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,yBAAyB,CAAC,CAAC;AAC5F,CAAC;AAED;;;GAGG;AAEI,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IAIe;IACa;IAJhD,QAAQ,CAA+D;IAEjF,YAC6C,MAAqB,EACR,YAAwC;QADrD,WAAM,GAAN,MAAM,CAAe;QACR,iBAAY,GAAZ,YAAY,CAA4B;QAEhG,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,qBAAqB,CAAC,CAAC;QAC/D,IAAI,CAAC,MAAM,CAAC,mBAAmB,CAAC,qBAAqB,EAAE,KAAK,CAAC,CAAC;IAChE,CAAC;IAED;;;OAGG;IACH,mBAAmB;QACjB,OAAO,CAAC,KAA+B,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAQ,EAAE;YAChG,0CAA0C;YAC1C,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;gBACpB,mEAAmE;gBACnE,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC;YACrB,CAAC;YAED,qDAAqD;YACrD,MAAM,gBAAgB,GAAI,GAAW,CAAC,gBAAqD,CAAC;YAE5F,MAAM,aAAa,GAAG,KAAK,YAAY,KAAK;gBAC1C,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,sBAAsB,CAAC,KAAK,EAAE,gBAAgB,CAAC;gBACnE,CAAC,CAAC,KAAK,CAAC;YACV,aAAa,CAAC,SAAS,GAAG,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC;YAEvD,yEAAyE;YACzE,sEAAsE;YACtE,oEAAoE;YACpE,sEAAsE;YACtE,wEAAwE;YACxE,uEAAuE;YACvE,uEAAuE;YACvE,wEAAwE;YACxE,wCAAwC,CACtC,IAAI,CAAC,MAAM,EACX,qBAAqB,EACrB,aAAa;YACb,qEAAqE;YACrE,sEAAsE;YACtE,iBAAiB;YACjB;gBACE,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,IAAI,EAAE,GAAG,CAAC,IAAI;aACf,CACF,CAAC;YAEF,IAAI,CAAC,iBAAiB,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;QAC7C,CAAC,CAAC;IACJ,CAAC;IAED,0BAA0B,CAAC,KAA+B,EAAE,gBAAwC;QAClG,MAAM,aAAa,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,sBAAsB,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QAEzH,2EAA2E;QAC3E,0EAA0E;QAC1E,4EAA4E;QAC5E,kCAAkC;QAClC,wCAAwC,CAAC,IAAI,CAAC,MAAM,EAAE,gCAAgC,EAAE,aAAa,CAAC,CAAC;IACzG,CAAC;IAGO,iBAAiB,CAAC,SAA6B,EAAE,GAAa,EAAE,YAA4C;QAClH,2CAA2C;QAC3C,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;YACpB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,mDAAmD,EAAE;gBACpE,SAAS,EAAE,SAAS,CAAC,IAAI;gBACzB,UAAU,EAAE,SAAS,CAAC,UAAU;aACjC,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,iFAAiF;YACjF,4GAA4G;YAC5G,EAAE;YACF,uFAAuF;YACvF,yFAAyF;YACzF,oFAAoF;YACpF,sFAAsF;YACtF,wFAAwF;YACxF,gEAAgE;YAChE,IAAI,gBAA8C,CAAC;YAEnD,6EAA6E;YAC7E,IAAI,SAAS,CAAC,IAAI,KAAK,SAAS,CAAC,QAAQ,IAAI,SAAS,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC;gBAC1E,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,SAAmB,CAAC;gBACxD,MAAM,cAAc,GAAG,SAAS,CAAC,OAAO,CAAC,cAAoC,CAAC;gBAC9E,MAAM,kBAAkB,GAAG,SAAS,CAAC,OAAO,CAAC,kBAAwC,CAAC;gBACtF,MAAM,eAAe,GAAG,kBAAkB;oBACxC,CAAC,CAAC,kBAAkB,CAAC,OAAO,CAAC,aAAa,EAAE,SAAS,CAAC;oBACtD,CAAC,CAAC,cAAc;wBACd,CAAC,CAAC,IAAI,cAAc,kBAAkB;wBACtC,CAAC,CAAC,sBAAsB,CAAC;gBAE7B,MAAM,KAAK,GAAyB;oBAClC,GAAG,EAAE,4BAA4B;oBACjC,OAAO,EAAE,eAAe;oBACxB,MAAM,EAAE;wBACN,SAAS;wBACT,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;qBACrD;iBACF,CAAC;gBACF,gBAAgB,GAAG,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC;YAC5C,CAAC;YAED,qFAAqF;YACrF,IAAI,SAAS,CAAC,IAAI,KAAK,SAAS,CAAC,UAAU,IAAI,SAAS,CAAC,OAAO,EAAE,SAAS,EAAE,CAAC;gBAC5E,MAAM,SAAS,GAAG,SAAS,CAAC,OAAO,CAAC,SAAkE,CAAC;gBACvG,gBAAgB,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;oBACjD,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;oBACvC,GAAG,CAAC,SAAS,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC;oBAC/B,OAAO,GAAG,CAAC;gBACb,CAAC,EAAE,EAAsB,CAAC,CAAC;YAC7B,CAAC;YAED,uFAAuF;YACvF,0FAA0F;YAC1F,MAAM,cAAc,GAAG,IAAI,CAAC,YAAY,CAAC,sBAAsB,CAAC,SAAS,CAAC,CAAC;YAC3E,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,EAAE,iBAAiB,CAAC;YACpE,IACE,CAAC,OAAO,iBAAiB,KAAK,QAAQ,IAAI,OAAO,iBAAiB,KAAK,QAAQ,CAAC;mBAC7E,iBAAiB,KAAK,EAAE;mBACxB,OAAO,GAAG,CAAC,SAAS,KAAK,UAAU;mBACnC,OAAO,GAAG,CAAC,SAAS,KAAK,UAAU;mBACnC,GAAG,CAAC,SAAS,CAAC,aAAa,CAAC,KAAK,SAAS,EAC7C,CAAC;gBACD,GAAG,CAAC,SAAS,CAAC,aAAa,EAAE,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC;YAC1D,CAAC;YAED,MAAM,QAAQ,GAAkB;gBAC9B,KAAK,EAAE;oBACL,IAAI,EAAE,SAAS,CAAC,IAAI;oBACpB,QAAQ,EAAE,SAAS,CAAC,QAAQ;oBAC5B,OAAO,EAAE,SAAS,CAAC,sBAAsB;oBACzC,aAAa,EAAE,SAAS,CAAC,sBAAsB;oBAC/C,GAAG,CAAC,SAAS,CAAC,sBAAsB,IAAI,EAAE,sBAAsB,EAAE,SAAS,CAAC,sBAAsB,EAAE,CAAC;oBACrG,aAAa,EAAE,SAAS,CAAC,gBAAgB;oBACzC,oFAAoF;oBACpF,uFAAuF;oBACvF,6FAA6F;oBAC7F,wFAAwF;oBACxF,GAAG,CAAC,OAAO,cAAc,CAAC,OAAO,EAAE,IAAI,KAAK,QAAQ,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;oBAC9F,8FAA8F;oBAC9F,kGAAkG;oBAClG,aAAa,EAAE,SAAS,CAAC,aAAa,IAAI,uBAAuB,EAAE;oBACnE,SAAS,EAAE,SAAS,CAAC,SAAS,CAAC,WAAW,EAAE;oBAC5C,YAAY,EAAE,SAAS,CAAC,YAAY;oBACpC,0CAA0C;oBAC1C,0BAA0B;oBAC1B,GAAG,CAAC,cAAc,CAAC,OAAO,IAAI,EAAE,OAAO,EAAE,cAAc,CAAC,OAAO,EAAE,CAAC;oBAClE,wEAAwE;oBACxE,GAAG,CAAC,gBAAgB,IAAI,EAAE,gBAAgB,EAAE,CAAC;oBAC7C,0DAA0D;oBAC1D,GAAG,CAAC,SAAS,CAAC,YAAY,IAAI,EAAE,YAAY,EAAE,SAAS,CAAC,YAAY,EAAE,CAAC;iBACxE;aACF,CAAC;YAEF,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAClD,CAAC;QAAC,OAAO,SAAS,EAAE,CAAC;YACnB,8DAA8D;YAC9D,wEAAwE;YACxE,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,+BAA+B,EAAE;gBACjD,aAAa,EAAE,SAAS,CAAC,IAAI;gBAC7B,SAAS,EAAE,mBAAmB,CAAC,SAAS,CAAC;aAC1C,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAGD;;OAEG;IACK,kBAAkB,CAAC,GAAY;QACrC,MAAM,aAAa,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,aAAa,CAAC,CAAC;QACjE,IAAI,aAAa,EAAE,CAAC;YAClB,OAAO,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACvF,CAAC;QAED,OAAO,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,OAAO,CAAW;YAC9C,GAAG,CAAC,UAAU,EAAE,aAAa;YAC7B,GAAG,CAAC,MAAM,EAAE,aAAa;YACzB,SAAS,CAAC;IACnB,CAAC;IAED;;OAEG;IACH,YAAY,CAAC,EAAY;QACvB,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAQ,EAAE;YAC/D,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAClD,CAAC,CAAC;IACJ,CAAC;IAED;;OAEG;IACH,wBAAwB;QACtB,OAAO,CAAC,EAAE,CAAC,oBAAoB,EAAE,CAAC,MAAW,EAAE,OAAqB,EAAE,EAAE;YACtE,MAAM,aAAa,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,mBAAmB,EAAE,SAAS,EAAE;gBAC3F,KAAK,EAAE,MAAM,EAAE,KAAK;gBACpB,OAAO,EAAE;oBACP,IAAI,EAAE,MAAM,EAAE,IAAI;oBAClB,OAAO,EAAE,MAAM,EAAE,OAAO;oBACxB,OAAO,EAAE,OAAO,CAAC,QAAQ,EAAE;iBAC5B;aACF,CAAC,CAAC;YACH,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,6BAA6B,EAAE,EAAE,GAAG,aAAa,EAAE,CAAC,CAAC;YAEvE,oBAAoB;YACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmDG;IACH,uBAAuB;QACrB,OAAO,CAAC,EAAE,CAAC,mBAAmB,EAAE,CAAC,KAAY,EAAE,EAAE;YAC/C,MAAM,aAAa,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,SAAS,CAAC,kBAAkB,EAAE,SAAS,EAAE;gBAC1F,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,OAAO,EAAE;oBACP,IAAI,EAAE,KAAK,CAAC,IAAI;oBAChB,OAAO,EAAE,KAAK,CAAC,OAAO;iBACvB;aACF,CAAC,CAAC;YAEH,IAAI,oCAAoC,CAAC,KAAK,CAAC,EAAE,CAAC;gBAChD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,sEAAsE,EAAE,EAAE,GAAG,aAAa,EAAE,CAAC,CAAC;gBAChH,OAAO;YACT,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,EAAE,EAAE,GAAG,aAAa,EAAE,CAAC,CAAC;YAE9D,oBAAoB;YACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;OAEG;IACH,wBAAwB;QACtB,IAAI,CAAC,wBAAwB,EAAE,CAAC;QAChC,IAAI,CAAC,uBAAuB,EAAE,CAAC;IACjC,CAAC;CAEF,CAAA;AA7SY,mBAAmB;IAD/B,UAAU,EAAE;IAKR,WAAA,MAAM,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAA;IACjC,WAAA,MAAM,CAAC,kBAAkB,CAAC,mBAAmB,CAAC,CAAA;qCADI,aAAa;QACM,0BAA0B;GALvF,mBAAmB,CA6S/B","sourcesContent":["import { NextFunction, Request, Response } from 'express';\nimport { inject, injectable } from 'inversify';\nimport { LoggerService } from '~wildo-backend-lib/monitoring/logger/services/logger.service';\nimport { SAAS_SERVICE_TYPES } from '~wildo-backend-lib/inject/models/service-types';\nimport { ErrorBuilderBackendService } from './error-builder.backend.service';\nimport { logWildoBackendErrorAtClassifiedSeverity, describeCaughtError } from '~wildo-backend-lib/errors/utils/error-log-level-classification.backend.utils';\nimport { getCurrentCorrelationId } from '~wildo-backend-lib/runtime/models/request-context.als.backend';\n\n// Import the unified error system\nimport {\n ErrorResponse,\n ErrorType,\n ExecutionContext_InitiatorIds,\n WildoError_Backend,\n ValidationErrors,\n ValidationErrorEntry,\n WildoHeaderKeys,\n} from '@wildo-ai/saas-models';\nimport { ExecutionContext, } from '~wildo-backend-lib/runtime/models/execution-context.backend.schemas';\n\n/**\n * Narrowly isolates uncaught exceptions that originate inside\n * `@opentelemetry/sdk-logs`'s background batch-flush timer from every other\n * uncaught exception. Matched ONLY by the presence of the `sdk-logs` package\n * segment in the stack trace — this is deliberately conservative: any error\n * whose stack does not clearly point into that package still hard-exits via\n * the default path, so a real host-state-corrupting bug is never masked.\n *\n * Root cause (2026-08-06): the timer callback in\n * `BatchLogRecordProcessorBase` does `this._flushOneBatch().then(...).catch(...)`\n * on a fixed interval, independent of any request. Observed in production dev\n * use as `TypeError: this._flushOneBatch(...).then(...).catch is not a\n * function`. The processor's own promise chain broke down before its internal\n * `globalErrorHandler` safety net could catch it, so the failure escaped as a\n * process-level uncaught exception — killing an otherwise-healthy host over a\n * background telemetry-export housekeeping tick.\n */\nexport function isNonFatalTelemetryPipelineException(error: Error): boolean {\n return typeof error.stack === 'string' && error.stack.includes('@opentelemetry/sdk-logs');\n}\n\n/**\n * Unified Error Handler Service\n * Handles all errors using the new unified error management system\n */\n@injectable()\nexport class ErrorHandlerService {\n protected logDebug: (message: string, context?: Record<string, unknown>) => void;\n\n constructor(\n @inject(SAAS_SERVICE_TYPES.Logger) private logger: LoggerService,\n @inject(SAAS_SERVICE_TYPES.ErrorBuilderService) private errorBuilder: ErrorBuilderBackendService\n ) {\n this.logDebug = this.logger.debugByType('ErrorHandlerService');\n this.logger.setDebugTypeLogging('ErrorHandlerService', false);\n }\n\n /**\n * Express error handling middleware with unified error system\n * Express requires exactly 4 parameters: (err, req, res, next)\n */\n expressErrorHandler(): (error: Error|WildoError_Backend, req: Request, res: Response, next: NextFunction) => void {\n return (error: Error|WildoError_Backend, req: Request, res: Response, next: NextFunction): void => {\n // Check if response has already been sent\n if (res.headersSent) {\n // If headers are already sent, delegate to Express default handler\n return next(error);\n }\n\n // Extract executionContext from request if available\n const executionContext = (req as any).executionContext as ExecutionContext<any> | undefined;\n\n const errorToHandle = error instanceof Error\n ? this.errorBuilder.buildFromStandardError(error, executionContext)\n : error;\n errorToHandle.ipAddress = this.getClientIpAddress(req);\n\n // Single, central log for every HTTP error — emitted at the *classified*\n // operational severity rather than a blanket `error`. This is the one\n // true HTTP error boundary (mounted globally in http-server and per\n // sub-router), so logging here guarantees exactly one line per failed\n // request: expected client conditions (e.g. an expired token the client\n // will refresh) stay at `debug`, genuine server faults stay at `error`\n // with a stack. Per-controller boundaries no longer log — they forward\n // here — which removes the historic double-logging and the noise flood.\n logWildoBackendErrorAtClassifiedSeverity(\n this.logger,\n 'HTTP request failed',\n errorToHandle,\n // Boundary-specific extras only — the error's own fields (errorType,\n // statusCode, customMessageReference, operationPath, …) are projected\n // by the helper.\n {\n method: req.method,\n path: req.path,\n },\n );\n\n this.sendErrorResponse(errorToHandle, res);\n };\n }\n\n handleBatchesCronjobsError(error: Error|WildoError_Backend, executionContext?: ExecutionContext<any>): void {\n const errorToHandle = error instanceof Error ? this.errorBuilder.buildFromStandardError(error, executionContext) : error;\n\n // Batch/cron is a non-HTTP boundary (no `expressErrorHandler` downstream),\n // so this is where the line must be emitted — at the classified severity.\n // The error's own fields are projected by the helper; the stack is attached\n // only when the level is `error`.\n logWildoBackendErrorAtClassifiedSeverity(this.logger, 'Batch/cronjob execution failed', errorToHandle);\n }\n\n\n private sendErrorResponse(saasError: WildoError_Backend, res: Response, initiatorIds?: ExecutionContext_InitiatorIds): void {\n // Double-check if headers are already sent\n if (res.headersSent) {\n this.logger.warn('Cannot send error response - headers already sent', {\n errorType: saasError.type,\n statusCode: saasError.statusCode\n });\n return;\n }\n\n try {\n // Build validationErrors for responses that carry field-level error information.\n // Values can be plain strings (Zod errors) or structured ValidationErrorEntry objects (unique constraints).\n //\n // C3: Intentional extraction order — validationErrors are extracted from the raw error\n // BEFORE sanitizeErrorForClient() runs. This is by design: zodIssues contain field-level\n // validation messages that must appear in the response for form UX, even though the\n // sanitization layer may strip them from `details`. If zodIssues are removed from the\n // sanitization allowlist in the future, validationErrors will still be included because\n // they are extracted independently above the sanitization call.\n let validationErrors: ValidationErrors | undefined;\n\n // CONFLICT (409): Unique constraint violations — use structured i18n entries\n if (saasError.type === ErrorType.CONFLICT && saasError.context?.fieldName) {\n const fieldName = saasError.context.fieldName as string;\n const duplicateValue = saasError.context.duplicateValue as string | undefined;\n const customErrorMessage = saasError.context.customErrorMessage as string | undefined;\n const fallbackMessage = customErrorMessage\n ? customErrorMessage.replace('{fieldName}', fieldName)\n : duplicateValue\n ? `\"${duplicateValue}\" already exists`\n : 'Value already exists';\n\n const entry: ValidationErrorEntry = {\n key: 'validation.unique.conflict',\n message: fallbackMessage,\n params: {\n fieldName,\n ...(duplicateValue ? { value: duplicateValue } : {}),\n },\n };\n validationErrors = { [fieldName]: entry };\n }\n\n // VALIDATION (400): Zod validation errors — plain strings (already localized by Zod)\n if (saasError.type === ErrorType.VALIDATION && saasError.context?.zodIssues) {\n const zodIssues = saasError.context.zodIssues as Array<{ path: (string | number)[]; message: string }>;\n validationErrors = zodIssues.reduce((acc, issue) => {\n const fieldPath = issue.path.join('.');\n acc[fieldPath] = issue.message;\n return acc;\n }, {} as ValidationErrors);\n }\n\n // H9 fix: Sanitize error at serialization boundary — backend logs retain full context,\n // but the HTTP response strips internal details (emails, stack traces, session IDs, etc.)\n const sanitizedError = this.errorBuilder.sanitizeErrorForClient(saasError);\n const retryAfterSeconds = sanitizedError.context?.retryAfterSeconds;\n if (\n (typeof retryAfterSeconds === 'number' || typeof retryAfterSeconds === 'string')\n && retryAfterSeconds !== ''\n && typeof res.getHeader === 'function'\n && typeof res.setHeader === 'function'\n && res.getHeader('Retry-After') === undefined\n ) {\n res.setHeader('Retry-After', String(retryAfterSeconds));\n }\n\n const response: ErrorResponse = {\n error: {\n type: saasError.type,\n severity: saasError.severity,\n message: saasError.customMessageReference,\n customMessage: saasError.customMessageReference,\n ...(saasError.customMessageReference && { customMessageReference: saasError.customMessageReference }),\n operationPath: saasError.operationPathKey,\n // A stable, operation-specific refusal code (e.g. `LAST_ORGANIZATION_OWNER`) is the\n // contract clients branch on. It is authored in `context.code` and allow-listed by the\n // sanitizer, so it also appears under `details.code`; hoisting it to the top-level field the\n // schema reserves is what lets the customer documentation say \"branch on `error.code`\".\n ...(typeof sanitizedError.context?.code === 'string' && { code: sanitizedError.context.code }),\n // Phase 3 (SD5) — a safe, non-PII support/trace id the user can quote; joins this response to\n // the backend logs + audit for the same request. The error's captured id, else the ambient frame.\n correlationId: saasError.correlationId ?? getCurrentCorrelationId(),\n timestamp: saasError.timestamp.toISOString(),\n initiatorIds: saasError.initiatorIds,\n // executionType: saasError.executionType,\n // stack: saasError.stack,\n ...(sanitizedError.context && { details: sanitizedError.context }),\n // FIX-P6-H1: Include validationErrors at top level for frontend parsing\n ...(validationErrors && { validationErrors }),\n // Phase 1b: Include user guidance if present on the error\n ...(saasError.userGuidance && { userGuidance: saasError.userGuidance }),\n }\n };\n\n res.status(saasError.statusCode).json(response);\n } catch (sendError) {\n // If sending the error response fails, log it but don't throw\n // This prevents Express default HTML error handler from being triggered\n this.logger.error('Failed to send error response', {\n originalError: saasError.type,\n sendError: describeCaughtError(sendError)\n });\n }\n }\n\n\n /**\n * Get client IP address\n */\n private getClientIpAddress(req: Request): string {\n const xForwardedFor = req.headers[WildoHeaderKeys.FORWARDED_FOR];\n if (xForwardedFor) {\n return Array.isArray(xForwardedFor) ? xForwardedFor[0] : xForwardedFor.split(',')[0];\n }\n\n return req.headers[WildoHeaderKeys.REAL_IP] as string ||\n req.connection?.remoteAddress ||\n req.socket?.remoteAddress ||\n 'unknown';\n }\n\n /**\n * Create async error wrapper for route handlers\n */\n asyncHandler(fn: Function): (req: Request, res: Response, next: NextFunction) => void {\n return (req: Request, res: Response, next: NextFunction): void => {\n Promise.resolve(fn(req, res, next)).catch(next);\n };\n }\n\n /**\n * Global unhandled rejection handler\n */\n handleUnhandledRejection(): void {\n process.on('unhandledRejection', (reason: any, promise: Promise<any>) => {\n const errorToHandle = this.errorBuilder.buildError(ErrorType.UNHANDLED_REJECTION, undefined, {\n stack: reason?.stack,\n context: {\n name: reason?.name,\n message: reason?.message,\n promise: promise.toString()\n }\n });\n this.logger.fatal('Unhandled Promise Rejection', { ...errorToHandle });\n\n // Graceful shutdown\n process.exit(1);\n });\n }\n\n /**\n * Global uncaught exception handler.\n *\n * Exits the process for every uncaught exception EXCEPT the narrow class\n * isolated by {@link isNonFatalTelemetryPipelineException}. Node's own\n * guidance (\"uncaught exception means unknown state, exit\") assumes the\n * failure came from the host's own request/business logic. A background\n * telemetry batch-flush timer (OTel's `BatchLogRecordProcessor`, ticking\n * every few seconds independently of any request) is a different failure\n * domain: its own state corruption says nothing about whether the host's\n * actual work is safe to continue, and `otel-preload.ts` already commits to\n * \"telemetry failures must never affect host availability\" for SDK-start\n * failures (Audit A-17) — this extends the same policy to a *runtime*\n * telemetry failure. Every other uncaught exception keeps today's hard-exit\n * behavior unchanged.\n *\n * NOTE: this method is not wired into any production entrypoint today (no\n * caller reaches `initializeGlobalHandlers`/`handleUncaughtException`) —\n * kept for any future/direct caller, and as the tested home of the shared\n * {@link isNonFatalTelemetryPipelineException} predicate. It cannot simply\n * become \"the\" global handler: it depends on DI (`ErrorBuilderBackendService`\n * via the container), so it is only constructible AFTER\n * `setupSaasBackendInjection()` — every real crash boundary below registers\n * a RAW `process.on(...)` listener at module load, before the container\n * exists, specifically so container-construction failures and early-startup\n * throws are still caught. The listeners that actually apply this predicate\n * in production, all against the SAME exported function:\n *\n * - `runPlatformServiceEntrypoint` (platform\n * `platform-shared-lib/src/core/startup/platform-service-process-boundary.ts`)\n * — protects `platform-apps-manager`, `platform-crontabs-batches-manager`,\n * `platform-module-registry`. This is the process that actually crashed\n * on 2026-08-06, on `this._flushOneBatch(...).then(...).catch is not a\n * function` inside `@opentelemetry/sdk-logs`'s batch timer.\n * - `WorkerShutdownManagerBackendService.registerSignalHandlers` (this\n * package, `queue/worker-shutdown-manager.backend.service.ts`) — the\n * engine-shared listener reached by every NON-PLATFORM host\n * (backend-api, workers, minions) that runs a queue worker OR has\n * telemetry active, via `StartApplicationBackendService`'s\n * `!isPlatformApplication`/`!isPlatformMode`-gated calls into\n * `ObservabilityBackendService.initialize`/`QueueWorkerInitializer.initialize`.\n * Platform hosts skip both gates entirely and are NOT covered by this\n * listener — they rely solely on the `runPlatformServiceEntrypoint` entry\n * above.\n * - The hand-rolled bootstrap `process.on('uncaughtException'/'unhandledRejection', ...)`\n * blocks in each application entrypoint (Wonder Todos:\n * `backend-api/src/index.ts`, `minions/marketing-scrapper/src/index.ts`,\n * `workers/my-node-worker/src/index.ts`) — these are hand-authored\n * per-app boilerplate, not generated from a shared template, so a future\n * app scaffolded the same way needs the same carve-out applied by hand\n * until this pattern is consolidated into a shared bootstrap helper.\n */\n handleUncaughtException(): void {\n process.on('uncaughtException', (error: Error) => {\n const errorToHandle = this.errorBuilder.buildError(ErrorType.UNCAUGHT_EXCEPTION, undefined, {\n stack: error.stack,\n context: {\n name: error.name,\n message: error.message,\n }\n });\n\n if (isNonFatalTelemetryPipelineException(error)) {\n this.logger.error('Uncaught exception isolated to the telemetry pipeline; host stays up', { ...errorToHandle });\n return;\n }\n\n this.logger.fatal('Uncaught Exception', { ...errorToHandle });\n\n // Graceful shutdown\n process.exit(1);\n });\n }\n\n /**\n * Initialize global error handlers\n */\n initializeGlobalHandlers(): void {\n this.handleUnhandledRejection();\n this.handleUncaughtException();\n }\n\n}\n"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public contracts for the {@link ExternalDataAdminBackendController}.
|
|
3
|
+
*
|
|
4
|
+
* Co-located away from the controller so tests, operators and ad-hoc scripts can consume the
|
|
5
|
+
* literal paths without instantiating the DI container — the same split, and the same reasons, as
|
|
6
|
+
* `data-seeding-admin.backend.contracts`. No runtime logic here, only literals and types.
|
|
7
|
+
*/
|
|
8
|
+
/** @wildo_source:part:start saas.external-data.admin-endpoints facet:kind:framework-api facet:audience:operator */
|
|
9
|
+
/**
|
|
10
|
+
* Environment variable holding the secret these endpoints require. When unset or empty EVERY
|
|
11
|
+
* endpoint answers 503 — the safe default, so a fresh deployment never accidentally exposes an
|
|
12
|
+
* operator surface that can reach a third party's production system.
|
|
13
|
+
*
|
|
14
|
+
* Deliberately the SAME key data-seeding uses: an operator administering one app has one admin
|
|
15
|
+
* secret, and minting a second would mean two rotations to remember and one of them forgotten.
|
|
16
|
+
*/
|
|
17
|
+
export declare const ExternalDataAdminEndpointEnvKey: "APPLICATION_ADMIN_SECRET";
|
|
18
|
+
/** Header carrying the secret. Hash-then-`timingSafeEqual`, per the sibling controller. */
|
|
19
|
+
export declare const ExternalDataAdminEndpointHeaderKey: "X-Admin-Secret";
|
|
20
|
+
/**
|
|
21
|
+
* Root mount. Under `/internal/...` rather than `/api/v1/...` because this is an operator surface,
|
|
22
|
+
* not a consumer API — the convention `/health` and the data-seeding admin surface already follow.
|
|
23
|
+
*/
|
|
24
|
+
export declare const ExternalDataAdminEndpointMountBasePath: "/internal/admin/external-data";
|
|
25
|
+
/** Sub-paths, relative to {@link ExternalDataAdminEndpointMountBasePath}. */
|
|
26
|
+
export declare const ExternalDataAdminEndpointPath: {
|
|
27
|
+
/** GET — every declared pipeline with its persisted cursor and last-run summary. */
|
|
28
|
+
readonly PIPELINES: "/pipelines";
|
|
29
|
+
/**
|
|
30
|
+
* POST — run ONE pipeline now. `{ "resourceType": "externalCustomers" }`.
|
|
31
|
+
*
|
|
32
|
+
* This exists because a scheduled-only capability is undevelopable: authoring a mapping against
|
|
33
|
+
* SAP or Dynamics means change → sync → look, dozens of times, and an hourly cron makes that loop
|
|
34
|
+
* unusable. Verifying the first dogfood required either waiting an hour or editing committed app
|
|
35
|
+
* config, which is what proved the door was missing.
|
|
36
|
+
*/
|
|
37
|
+
readonly RUN: "/run";
|
|
38
|
+
};
|
|
39
|
+
export type ExternalDataAdminEndpointPathValue = (typeof ExternalDataAdminEndpointPath)[keyof typeof ExternalDataAdminEndpointPath];
|
|
40
|
+
/** @wildo_source:part:end saas.external-data.admin-endpoints */
|
|
41
|
+
//# sourceMappingURL=external-data-admin.backend.contracts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"external-data-admin.backend.contracts.d.ts","sourceRoot":"","sources":["../../../../../src/external-data/controllers/external-data-admin.backend.contracts.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,mHAAmH;AACnH;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,EAAG,0BAAmC,CAAC;AAEnF,2FAA2F;AAC3F,eAAO,MAAM,kCAAkC,EAAG,gBAAyB,CAAC;AAE5E;;;GAGG;AACH,eAAO,MAAM,sCAAsC,EAAG,+BAAwC,CAAC;AAE/F,6EAA6E;AAC7E,eAAO,MAAM,6BAA6B;IACxC,oFAAoF;;IAEpF;;;;;;;OAOG;;CAEK,CAAC;AAEX,MAAM,MAAM,kCAAkC,GAC5C,CAAC,OAAO,6BAA6B,CAAC,CAAC,MAAM,OAAO,6BAA6B,CAAC,CAAC;AACrF,gEAAgE"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public contracts for the {@link ExternalDataAdminBackendController}.
|
|
3
|
+
*
|
|
4
|
+
* Co-located away from the controller so tests, operators and ad-hoc scripts can consume the
|
|
5
|
+
* literal paths without instantiating the DI container — the same split, and the same reasons, as
|
|
6
|
+
* `data-seeding-admin.backend.contracts`. No runtime logic here, only literals and types.
|
|
7
|
+
*/
|
|
8
|
+
/** @wildo_source:part:start saas.external-data.admin-endpoints facet:kind:framework-api facet:audience:operator */
|
|
9
|
+
/**
|
|
10
|
+
* Environment variable holding the secret these endpoints require. When unset or empty EVERY
|
|
11
|
+
* endpoint answers 503 — the safe default, so a fresh deployment never accidentally exposes an
|
|
12
|
+
* operator surface that can reach a third party's production system.
|
|
13
|
+
*
|
|
14
|
+
* Deliberately the SAME key data-seeding uses: an operator administering one app has one admin
|
|
15
|
+
* secret, and minting a second would mean two rotations to remember and one of them forgotten.
|
|
16
|
+
*/
|
|
17
|
+
export const ExternalDataAdminEndpointEnvKey = 'APPLICATION_ADMIN_SECRET';
|
|
18
|
+
/** Header carrying the secret. Hash-then-`timingSafeEqual`, per the sibling controller. */
|
|
19
|
+
export const ExternalDataAdminEndpointHeaderKey = 'X-Admin-Secret';
|
|
20
|
+
/**
|
|
21
|
+
* Root mount. Under `/internal/...` rather than `/api/v1/...` because this is an operator surface,
|
|
22
|
+
* not a consumer API — the convention `/health` and the data-seeding admin surface already follow.
|
|
23
|
+
*/
|
|
24
|
+
export const ExternalDataAdminEndpointMountBasePath = '/internal/admin/external-data';
|
|
25
|
+
/** Sub-paths, relative to {@link ExternalDataAdminEndpointMountBasePath}. */
|
|
26
|
+
export const ExternalDataAdminEndpointPath = {
|
|
27
|
+
/** GET — every declared pipeline with its persisted cursor and last-run summary. */
|
|
28
|
+
PIPELINES: '/pipelines',
|
|
29
|
+
/**
|
|
30
|
+
* POST — run ONE pipeline now. `{ "resourceType": "externalCustomers" }`.
|
|
31
|
+
*
|
|
32
|
+
* This exists because a scheduled-only capability is undevelopable: authoring a mapping against
|
|
33
|
+
* SAP or Dynamics means change → sync → look, dozens of times, and an hourly cron makes that loop
|
|
34
|
+
* unusable. Verifying the first dogfood required either waiting an hour or editing committed app
|
|
35
|
+
* config, which is what proved the door was missing.
|
|
36
|
+
*/
|
|
37
|
+
RUN: '/run',
|
|
38
|
+
};
|
|
39
|
+
/** @wildo_source:part:end saas.external-data.admin-endpoints */
|
|
40
|
+
//# sourceMappingURL=external-data-admin.backend.contracts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"external-data-admin.backend.contracts.js","sourceRoot":"","sources":["../../../../../src/external-data/controllers/external-data-admin.backend.contracts.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,mHAAmH;AACnH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,0BAAmC,CAAC;AAEnF,2FAA2F;AAC3F,MAAM,CAAC,MAAM,kCAAkC,GAAG,gBAAyB,CAAC;AAE5E;;;GAGG;AACH,MAAM,CAAC,MAAM,sCAAsC,GAAG,+BAAwC,CAAC;AAE/F,6EAA6E;AAC7E,MAAM,CAAC,MAAM,6BAA6B,GAAG;IAC3C,oFAAoF;IACpF,SAAS,EAAE,YAAY;IACvB;;;;;;;OAOG;IACH,GAAG,EAAE,MAAM;CACH,CAAC;AAIX,gEAAgE","sourcesContent":["/**\n * Public contracts for the {@link ExternalDataAdminBackendController}.\n *\n * Co-located away from the controller so tests, operators and ad-hoc scripts can consume the\n * literal paths without instantiating the DI container — the same split, and the same reasons, as\n * `data-seeding-admin.backend.contracts`. No runtime logic here, only literals and types.\n */\n\n/** @wildo_source:part:start saas.external-data.admin-endpoints facet:kind:framework-api facet:audience:operator */\n/**\n * Environment variable holding the secret these endpoints require. When unset or empty EVERY\n * endpoint answers 503 — the safe default, so a fresh deployment never accidentally exposes an\n * operator surface that can reach a third party's production system.\n *\n * Deliberately the SAME key data-seeding uses: an operator administering one app has one admin\n * secret, and minting a second would mean two rotations to remember and one of them forgotten.\n */\nexport const ExternalDataAdminEndpointEnvKey = 'APPLICATION_ADMIN_SECRET' as const;\n\n/** Header carrying the secret. Hash-then-`timingSafeEqual`, per the sibling controller. */\nexport const ExternalDataAdminEndpointHeaderKey = 'X-Admin-Secret' as const;\n\n/**\n * Root mount. Under `/internal/...` rather than `/api/v1/...` because this is an operator surface,\n * not a consumer API — the convention `/health` and the data-seeding admin surface already follow.\n */\nexport const ExternalDataAdminEndpointMountBasePath = '/internal/admin/external-data' as const;\n\n/** Sub-paths, relative to {@link ExternalDataAdminEndpointMountBasePath}. */\nexport const ExternalDataAdminEndpointPath = {\n /** GET — every declared pipeline with its persisted cursor and last-run summary. */\n PIPELINES: '/pipelines',\n /**\n * POST — run ONE pipeline now. `{ \"resourceType\": \"externalCustomers\" }`.\n *\n * This exists because a scheduled-only capability is undevelopable: authoring a mapping against\n * SAP or Dynamics means change → sync → look, dozens of times, and an hourly cron makes that loop\n * unusable. Verifying the first dogfood required either waiting an hour or editing committed app\n * config, which is what proved the door was missing.\n */\n RUN: '/run',\n} as const;\n\nexport type ExternalDataAdminEndpointPathValue =\n (typeof ExternalDataAdminEndpointPath)[keyof typeof ExternalDataAdminEndpointPath];\n/** @wildo_source:part:end saas.external-data.admin-endpoints */\n"]}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ExternalDataAdminBackendController` — the operator door onto external-data pipelines.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this exists, and why it is not merely "nice to have"
|
|
5
|
+
*
|
|
6
|
+
* A pipeline could only ever be started by its own `scheduleCron`. That makes the capability
|
|
7
|
+
* UNDEVELOPABLE against a real remote system: authoring a mapping for SAP or Dynamics is a loop of
|
|
8
|
+
* change → sync → look, run dozens of times, and an hourly cron makes each turn of that loop an
|
|
9
|
+
* hour long. The gap was not theoretical — verifying Wonder Todos' first pipeline required either
|
|
10
|
+
* waiting for the hour or editing committed app config, which is what showed the door was missing.
|
|
11
|
+
*
|
|
12
|
+
* It is equally the operator surface: "the client list is wrong, sync it now" has no answer when
|
|
13
|
+
* the only trigger is a clock.
|
|
14
|
+
*
|
|
15
|
+
* ## Posture — deliberately identical to the data-seeding admin surface
|
|
16
|
+
*
|
|
17
|
+
* Root-mounted under `/internal/...` rather than `/api/v1/...` (operator surface, not consumer
|
|
18
|
+
* API), gated on `APPLICATION_ADMIN_SECRET` — the SAME key data-seeding uses, because an operator
|
|
19
|
+
* administering one application should hold one secret, and minting a second means two rotations to
|
|
20
|
+
* remember and one of them forgotten. Unset ⇒ every endpoint answers 503, so a fresh deployment
|
|
21
|
+
* never accidentally exposes a door that reaches a third party's production system.
|
|
22
|
+
*
|
|
23
|
+
* The secret compare hashes BOTH sides before `timingSafeEqual`, so the buffers are always 32 bytes
|
|
24
|
+
* and no length-mismatch timing oracle exists (the sibling's audit L-2 lesson, inherited rather
|
|
25
|
+
* than re-derived).
|
|
26
|
+
*
|
|
27
|
+
* There is deliberately NO in-process rate limit: like the sibling, this belongs behind a
|
|
28
|
+
* network-level limiter, and a run is already single-flighted per pipeline by the run service, so a
|
|
29
|
+
* caller hammering `/run` gets `already-running` rather than a stampede on the remote system.
|
|
30
|
+
*/
|
|
31
|
+
import 'reflect-metadata';
|
|
32
|
+
import { AbstractControllerBackendService } from '../../base-classes/services/controllers/abstract-controller.backend.service';
|
|
33
|
+
import { ErrorBuilderBackendService } from '../../errors/services/error-builder.backend.service';
|
|
34
|
+
import { ErrorHandlerService } from '../../errors/services/error-handler.backend.service';
|
|
35
|
+
import { LoggerService } from '../../monitoring/logger/services/logger.service';
|
|
36
|
+
import { ExecutionContextCreatorBackendService } from '../../runtime/services/execution-context-creator.backend.service';
|
|
37
|
+
import { RepositoriesRegistryHandlerBackendService } from '../../base-classes/services/repositories/repositories-registry-handler.backend.service';
|
|
38
|
+
import type { ExternalDataPipelineRunBackendService } from '../../external-data/external-data-pipeline-run.backend.service';
|
|
39
|
+
export declare class ExternalDataAdminBackendController extends AbstractControllerBackendService {
|
|
40
|
+
private readonly pipelineRunService;
|
|
41
|
+
private readonly repositoriesRegistry;
|
|
42
|
+
private readonly executionContextCreator;
|
|
43
|
+
constructor(logger: LoggerService, errorBuilder: ErrorBuilderBackendService, errorHandler: ErrorHandlerService, pipelineRunService: ExternalDataPipelineRunBackendService, repositoriesRegistry: RepositoriesRegistryHandlerBackendService, executionContextCreator: ExecutionContextCreatorBackendService);
|
|
44
|
+
/** Root-level mount, mirroring `/health` and the data-seeding admin surface. */
|
|
45
|
+
getCustomMountBasePath(): string;
|
|
46
|
+
protected setupRoutes(): void;
|
|
47
|
+
/**
|
|
48
|
+
* Same guard as the data-seeding admin surface, for the same reasons. Hash BOTH inputs so the
|
|
49
|
+
* buffers handed to `timingSafeEqual` are a fixed 32 bytes — an early length compare would leak
|
|
50
|
+
* the secret's length.
|
|
51
|
+
*/
|
|
52
|
+
private requireAdminSecret;
|
|
53
|
+
/**
|
|
54
|
+
* GET /pipelines — every DECLARED pipeline, joined with its persisted state.
|
|
55
|
+
*
|
|
56
|
+
* Declaration-first rather than state-first on purpose: a pipeline that has never run has no
|
|
57
|
+
* state row, and it is exactly the one an operator is looking for when something is wrong.
|
|
58
|
+
* Listing from state would make it invisible.
|
|
59
|
+
*/
|
|
60
|
+
private handleListPipelines;
|
|
61
|
+
/** POST /run — run one pipeline now, and answer with the run report. */
|
|
62
|
+
private handleRunPipeline;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=external-data-admin.backend.controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"external-data-admin.backend.controller.d.ts","sourceRoot":"","sources":["../../../../../src/external-data/controllers/external-data-admin.backend.controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,kBAAkB,CAAC;AAO1B,OAAO,EAAE,gCAAgC,EAAE,MAAM,0FAA0F,CAAC;AAC5I,OAAO,EAAE,0BAA0B,EAAE,MAAM,kEAAkE,CAAC;AAC9G,OAAO,EAAE,mBAAmB,EAAE,MAAM,kEAAkE,CAAC;AACvG,OAAO,EAAE,aAAa,EAAE,MAAM,8DAA8D,CAAC;AAE7F,OAAO,EAAE,qCAAqC,EAAE,MAAM,+EAA+E,CAAC;AACtI,OAAO,EAAE,yCAAyC,EAAE,MAAM,qGAAqG,CAAC;AAKhK,OAAO,KAAK,EAAE,qCAAqC,EAAE,MAAM,6EAA6E,CAAC;AAczI,qBACa,kCAAmC,SAAQ,gCAAgC;IAMpF,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IAEnC,OAAO,CAAC,QAAQ,CAAC,oBAAoB;IAErC,OAAO,CAAC,QAAQ,CAAC,uBAAuB;gBARL,MAAM,EAAE,aAAa,EACR,YAAY,EAAE,0BAA0B,EACxC,YAAY,EAAE,mBAAmB,EAEhE,kBAAkB,EAAE,qCAAqC,EAEzD,oBAAoB,EAAE,yCAAyC,EAE/D,uBAAuB,EAAE,qCAAqC;IAKjF,gFAAgF;IACzE,sBAAsB,IAAI,MAAM;IAIvC,SAAS,CAAC,WAAW,IAAI,IAAI;IAgB7B;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAgC1B;;;;;;OAMG;YACW,mBAAmB;IAmCjC,wEAAwE;YAC1D,iBAAiB;CA6BhC"}
|