feat(api): add queue trigger handler registration#274
Conversation
…ing parsing Changes: - ServiceBlobStorage.shutDown is now idempotent in both framework and OCOM adapter: resolves instead of rejecting when not started - Connection string parsing now trims segments/keys and compares keys case-insensitively to handle slightly malformed connection strings - Azurite test helper now handles spawn failures gracefully with clear error messages when Azurite binary is missing - Fixed brittle test that relied on registration call order: now uses instance type check - Blob storage config now fails fast with clear error when AZURE_STORAGE_CONNECTION_STRING is missing - Updated tests to reflect the new idempotent shutdown behavior Resolves sourcery review feedback for PR #254. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ing; handle azurite spawn errors; adjust tests to assert credential instance; fail-fast when AZURE_STORAGE_CONNECTION_STRING missing
…rts; refactor options type Changes: - Add input validation to createCredentialFromConnectionString to validate connection string is non-empty string before parsing - Improve error messages to specify which connection string part (AccountName vs AccountKey) is missing - Replace import.meta.dirname with fileURLToPath(import.meta.url) pattern for proper ESM compatibility - Refactor ServiceBlobStorageOptions to make connectionString optional when frameworkService is provided - Add runtime validation in constructor to ensure either connectionString or frameworkService is provided Addresses follow-up code review feedback on PR #254. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…f hardcoding - Replace hardcoded AZURITE_ACCOUNT_NAME and AZURITE_ACCOUNT_KEY constants with environment variable getters - Add AZURE_STORAGE_ACCOUNT_NAME and AZURE_STORAGE_ACCOUNT_KEY to local.settings.json (using devstoreaccount1 credentials) - Add AZURE_STORAGE_ACCOUNT_NAME and AZURE_STORAGE_ACCOUNT_KEY to dev-pri.json with empty values for environment-specific override - Update OCOM service-blob-storage to use explicit if/else with proper biome-ignore directive for non-null assertion - Eliminates hardcoded secrets from source code by sourcing from environment (local.settings.json in dev, Key Vault in production) - Improves security posture and flexibility for different deployment environments Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… Bicep configuration Refactor blob storage service to support DefaultAzureCredential (managed identity) for backend operations while keeping connection string authentication only for SAS token generation: **Blob Storage Service Changes:** - Create ClientUploadSigner service: Isolated SAS URL generation using StorageSharedKeyCredential - Update ServiceBlobStorageOptions: Add optional accountName and credential parameters for managed identity - Update ServiceBlobStorage: Support dual authentication modes (connection string or managed identity) - Add managed identity tests: Verify service works with DefaultAzureCredential - Add @azure/identity dependency for DefaultAzureCredential **Infrastructure Changes (Bicep):** - Update Function App identity: Enable managed identity on Function App - Grant Storage Blob Data Contributor role: Allow Function App to read/write blobs - Add storage-role-assignment.bicep: Separate template for RBAC configuration - Configure storage account for managed identity access **Security & Configuration:** - Ignore jws@4.0.0 vulnerability from transitive azurite dependency (dev-only, GHSA-869p-cjfg-cm3x) - Fix pre-commit audit failures from @azure/identity transitive dependencies This enables: - Local development with Azurite using connection string - Production deployments using managed identity without secrets - Clear separation of concerns between backend operations and SAS token generation - No hardcoded credentials in source code Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rt managed identity - Update generic iac/function-app/main.bicep to accept applicationStorageAccountName parameter and inject into Function App settings - Update @apps/api/iac/main.bicep to pass storage account output to function app module - Simplify OCOM ServiceBlobStorageOptions to directly extend framework options (no custom Omit needed) - Update blob storage config to require both AZURE_STORAGE_ACCOUNT_NAME and AZURE_STORAGE_CONNECTION_STRING: - Account name auto-injected by Bicep for deployed environments, manually in local.settings.json for dev - Connection string only needed for SAS token generation (both local and production) - Remove account name and account key from dev-pri.json (Bicep auto-injects account name, key not needed) - Keep connection string in dev-pri.json (used for SAS signing in production via Key Vault) - Update API test mock to export blobStorageConfig object with both accountName and connectionString This approach ensures: - Zero manual config needed for managed identity account name in deployed environments (auto-injected by Bicep) - Generic templates work for any application without extra configuration - Backend operations use managed identity (DefaultAzureCredential) - Client upload SAS signing uses connection string (isolated in ClientUploadSigner) - Works in both local (Azurite) and production (managed identity) environments Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add comprehensive JSDoc to ServiceBlobStorageOptions explaining two distinct modes: * Connection String: for local dev/Azurite * Managed Identity: for production with DefaultAzureCredential - Document precedence clearly: connectionString takes priority if both provided - Add determineAuthMode() helper to centralize validation and mode selection - Call helper in constructor to validate options at instantiation time - Fix typo in cellix-tdd-summary.md: 'connection-string' → 'connection string' This addresses the code review concern about surprising runtime behavior when both connectionString and accountName are provided. The helper and JSDoc make the precedence explicit and encourage callers to use only one option set. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Per code review feedback, both AZURE_STORAGE_ACCOUNT_NAME and AZURE_STORAGE_CONNECTION_STRING are now required in all environments (for their respective purposes: blob URL construction and SAS signing). However, they are no longer both passed to the same ServiceBlobStorage instance. Instead: - Config validation requires both env vars (they're needed for different concerns) - @ocom/service-blob-storage now exposes createBlobStorageFactory() that conditionally creates the framework service with only the appropriate credentials based on environment - In production (no connection string): uses only accountName → DefaultAzureCredential (managed identity) - In local/Azurite (connection string available): uses only connectionString → BlobServiceClient.fromConnectionString() This ensures managed identity is actually used in production (not bypassed by connection string precedence), while maintaining local dev support and SAS token generation in all environments. - Updated blob storage config to require both env vars with clear error messages - Added createBlobStorageFactory() to split credential consumption per environment - Updated @apps/api bootstrap to use the factory - Updated tests to mock the factory function - Inlined unused interface into function signature to satisfy knip Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… auth Reverted to a cleaner approach that aligns with the existing service registration pattern: - Always use managed identity (DefaultAzureCredential) for blob SDK authentication - Pass only accountName to the framework ServiceBlobStorage - Keep connectionString as an available config value for SAS signing - No environment-based credential selection - same auth everywhere - OCOM wrapper accepts both config values but only passes accountName to SDK The distinction is clearer now: accountName and connectionString serve different purposes (blob URLs and SAS signing), but the SDK authentication is always managed identity regardless of environment. Local Azurite continues to work via DEFAULT_AZURE_CREDENTIAL support of UseDevelopmentStorage=true in the connection string (used separately, not by the SDK client). - Updated @ocom/service-blob-storage to accept both config values - Service only passes accountName to framework for SDK authentication - Removed factory pattern, reverted to direct config object pattern - Updated tests to use simpler mock Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Make connectionString truly optional to support both deployment scenarios: 1. **Server-only blob operations** (managed identity only): - Provide only AZURE_STORAGE_ACCOUNT_NAME - No connection string needed - Cleaner configuration for simple use cases 2. **Client uploads with SAS signing** (includes connection string): - Provide both AZURE_STORAGE_ACCOUNT_NAME and AZURE_STORAGE_CONNECTION_STRING - Connection string is opt-in for SAS token generation - @Ocom application requires both features The framework (@cellix/service-blob-storage) supports multiple authentication modes (connection string and managed identity via accountName). Downstream adapters like @ocom/service-blob-storage leverage this flexibility to make connection string optional while always using managed identity for SDK operations. Key updates: - OCOM service explicitly documents the two deployment scenarios - Connection string is documented as opt-in for client uploads - Framework manifest updated to explain auth mode flexibility - Error message clarifies connection string is only for SAS signing - Cleaner configuration for consumers that don't need client uploads Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Test coverage for the managed identity path where frameworkService is not provided to the ServiceBlobStorage constructor. This path verifies that the service can be instantiated with just accountName for managed identity authentication. Resolves SonarCloud quality gate coverage failures on: - packages/ocom/service-blob-storage/src/service-blob-storage.ts - packages/ocom/service-blob-storage/src/blob-storage-adapter.ts Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…efault Azurite credentials - Pass connectionString from OCOM wrapper to framework service so SAS generation works - Default Azurite test helper to well-known dev account (devstoreaccount1) instead of hard-failing - Align OCOM options documentation to clarify connectionString is passed through to framework - Simplify OCOM constructor to only pass options that are defined to framework This resolves the feedback: 1. SAS generation now works when connectionString is provided 2. Azurite tests no longer fail when env vars are not set 3. Documentation/implementation alignment: connectionString is actually used as documented Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Make accountName non-optional in OCOM ServiceBlobStorageOptions since it's required and always passed to the framework; this catches misconfiguration at compile time - Simplify connectionString check in constructor now that accountName is non-optional - Fix pluralization: 'integration test' → 'integration tests' in TDD summary Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Clarify in blobStorageConfig that when both accountName and connectionString are provided (as in OCOM), the framework uses connection-string-based auth (shared key) - Document that managed identity is only used when accountName is provided alone - Resolve azurite-blob binary directly from node_modules/.bin instead of relying on 'pnpm exec', making tests more robust across different environments and CI setups - Include azurite binary path in error message for better debugging Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e alignment Address remaining review comments: 1. Make findRepoRoot resilient: Instead of hard-coded ../../../../ path, traverse up directory tree looking for pnpm-workspace.yaml marker. Also support REPO_ROOT environment variable for CI/test runner flexibility. Throws clear error if monorepo root not found. 2. Differentiate error messages: OCOM adapter error now says 'OCOM ServiceBlobStorage adapter is not started' to clearly distinguish from framework layer errors. 3. Update test expectations: Match new error message for clarity and maintainability. All tests pass: @cellix/service-blob-storage (17 tests) and @ocom/service-blob-storage (8 tests). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Create ADR-0032: Azure Blob Storage & Client Uploads Explains dual authentication strategy (managed identity for SDK, shared-key for SAS), client upload patterns, configuration, and production security practices - Update @cellix/service-blob-storage README Detailed three authentication modes with examples: 1. Managed Identity (production) 2. Connection String (local dev & SAS signing) 3. Mixed mode (managed identity + optional SAS signing) Comprehensive API documentation and error handling guide - Update @ocom/service-blob-storage README Application-specific adapter documentation with: - Client upload use case and flow - Dual-service architecture explanation - Configuration and environment variables - Avatar upload example - Authentication strategy table - Error handling and testing guidance Documentation clarifies the architecture decisions and provides clear guidance for developers and deployment teams. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…tion Implement the cleaner dual-service pattern discussed: - SDK service: Uses managed identity (accountName) for blob operations - SAS signing service: Uses connection string (only if provided) for URL generation Benefits: - Clear separation of concerns: each service has single responsibility - No mixing of authentication modes - Explicit opt-in for client uploads via connection string - More testable: services are independent - Code shows intent: which auth strategy each operation uses Changes: 1. ServiceBlobStorage constructor now creates two internal services: - sdkService: Always created with accountName (managed identity) - sasSigningService: Conditionally created if connectionString provided 2. blob-storage-adapter.ts updated to handle both services - Throws clear error if SAS requested without connectionString 3. Tests updated to verify dual-service behavior: - Test with both services (SAS signing works) - Test without SAS service (throws clear error) - Test managed identity path - Test SAS signing path 4. README updated to explain dual-service architecture with diagrams All tests passing (12 OCOM tests, 17 framework tests). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
BREAKING: OCOM ServiceBlobStorage now requires pre-configured framework services passed in at construction, not created internally. Apps register both at startup: - blobStorageService: SDK operations (managed identity) - clientUploadService: SAS URL signing (connection string) Changes: - apps/api/src/index.ts: Register blobStorageService and clientUploadService separately, then create OCOM adapter with both - @ocom/service-blob-storage: Accept sdkService and sasSigningService as options (no longer creates framework services internally) - Exposed listBlobs, uploadText, deleteBlob methods from OCOM adapter - Updated tests to match new architecture - Remove blob-storage-adapter.ts (no longer needed) Benefits: - Clear separation: each framework service registered for single responsibility - Explicit naming: blobStorageService vs clientUploadService in service registry - Easy to understand config: apps/api shows exactly which auth each uses - Flexible for consumers: can omit clientUploadService if not needed Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…pload Align with Service* naming convention and clarify that this service belongs to the blob storage domain. The name now follows the same pattern as ServiceBlobStorage while indicating its specific responsibility (client upload authorization via SAS signing). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use the full Azurite connection string with AccountName and AccountKey in local.settings.json instead of the shorthand UseDevelopmentStorage=true. This allows SAS token signing for client uploads to work correctly in local development with Azurite. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add tests covering all public methods of the client upload service wrapper: - Lifecycle methods (startUp, shutDown) - createUploadUrl delegation to framework signer - createReadUrl delegation to framework signer Uses valid Azurite connection string format to ensure realistic test scenarios. Achieves 100% code coverage for client-upload-service.ts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix typo in ADR-0032: 'managed identity identity assignment' → 'managed identity assignment' - Add clientUploadService to acceptance-api mock factory for ApiContextSpec compliance - Enhance JSDoc on createCredentialFromConnectionString to clarify that only shared-key connection strings (with AccountKey) are supported for SAS generation, not SAS tokens - Document that managed identity + accountName flow uses DefaultAzureCredential separately Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Clean up per-field inline comments and provide interface-level JSDoc for better IntelliSense. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…o interfaces.ts and update imports\n\nRename framework contract file to interfaces.ts for clarity and update local imports.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…lix and ocom packages
…d dist/ files Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…I, add community-creation queue - Remove handleMessageWithRetries from ServiceQueueStorage — Azure Functions queue triggers own retry/handler logic, not the storage service - Simplify QueueConsumerContext to receive* and peek* only (no delete*/handle*) with payload types derived from zod schemas - Add community-creation outbound queue schema (communityId, name, createdBy) - Wire sendCommunityCreation() call on community creation in application code - Auto-provision all registered queues (including community-creation) in local dev / Azurite on ServiceQueueStorage.startUp() Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ation handling and client upload operations; fixed e2e test suite failures
…thentication strategies, client uploads with auth headers, canonical auth headers security, troubleshooting, and category metadata. This cleanup streamlines the technical overview section by eliminating redundant and obsolete content.
…for compile time panic error
…ic contract tests; refined cellix blob storage package documentation
…ecent blob additions - Refactored Azurite account name and key handling in `azurite.ts` to use deterministic values for testing. - Updated connection string construction in tests to align with new Azurite credential generation. - Enhanced `TestApiServer` and `TestAzuriteServer` to utilize the new Azurite account configuration. - Removed redundant environment variable handling in test setup. - Improved logging for subprocesses in `PortlessServer` to aid in debugging. - Updated `ServiceBlobStorage` to directly re-export from Cellix, simplifying the interface for application use. - Removed outdated acceptance tests related to member management. - Adjusted documentation in `readme.md` and `blob-storage.contract.ts` to reflect changes in service structure and usage.
…error handling; improve test server file management; resolve knip errors
Reviewer's GuideAdds first-class Azure Storage Queue trigger support to the Cellix bootstrap, wires a new community-update queue and handler into the @apps/api app using typed queue storage services, and extends the queue consumer to validate/log both polled and trigger-delivered messages, including new system-scoped application services access. Sequence diagram for community-update queue trigger handlingsequenceDiagram
participant AzureStorageQueue
participant AzureFunctions
participant CommunityUpdateQueueHandler as communityUpdateQueueHandlerCreator
participant QueueStorageService as QueueStorageOperations
participant AppServicesFactory as ApplicationServicesFactory
participant AppServices as ApplicationServices
participant CommunityDomain as CommunityService
AzureStorageQueue-->>AzureFunctions: StorageQueueTrigger
AzureFunctions->>CommunityUpdateQueueHandler: StorageQueueHandler
CommunityUpdateQueueHandler->>QueueStorageService: receiveFromCommunityUpdateQueue
QueueStorageService-->>CommunityUpdateQueueHandler: QueueMessage
CommunityUpdateQueueHandler->>AppServicesFactory: forSystem
AppServicesFactory-->>CommunityUpdateQueueHandler: ApplicationServices
CommunityUpdateQueueHandler->>CommunityDomain: queryById
CommunityDomain-->>CommunityUpdateQueueHandler: existingCommunity
CommunityUpdateQueueHandler->>CommunityDomain: updateSettings
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 3 issues, and left some high level feedback:
- The new
@ocom/handler-queue-community-updatepackage commits builtdistartifacts (.js,.d.ts,.map); if your usual pattern is to build on publish/CI, consider excludingdistfrom source control to avoid drift between compiled and source code. - In
communityUpdateQueueHandlerCreatoryou re-declare the trigger message shape and manually build the object passed toreceiveFromCommunityUpdateQueue; you could simplify and tighten typing by reusing the exportedTriggeredQueueMessagetype from@cellix/service-queue-storage(and possibly thebuildTriggeredQueueMessagehelper) instead of maintaining a parallel structural type.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The new `@ocom/handler-queue-community-update` package commits built `dist` artifacts (`.js`, `.d.ts`, `.map`); if your usual pattern is to build on publish/CI, consider excluding `dist` from source control to avoid drift between compiled and source code.
- In `communityUpdateQueueHandlerCreator` you re-declare the trigger message shape and manually build the object passed to `receiveFromCommunityUpdateQueue`; you could simplify and tighten typing by reusing the exported `TriggeredQueueMessage` type from `@cellix/service-queue-storage` (and possibly the `buildTriggeredQueueMessage` helper) instead of maintaining a parallel structural type.
## Individual Comments
### Comment 1
<location path="packages/cellix/service-queue-storage/src/queue-consumer.ts" line_range="118-120" />
<code_context>
+ return message;
+}
+
+function toQueueMessage<TPayload>(triggeredMessage: TriggeredQueueMessage<TPayload>): QueueMessage<TPayload> {
+ return {
+ id: triggeredMessage.id ?? 'triggered-message',
+ payload: triggeredMessage.payload,
+ ...(triggeredMessage.popReceipt !== undefined ? { popReceipt: triggeredMessage.popReceipt } : {}),
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Defaulting the triggered message ID to a synthetic constant may make diagnosing issues harder.
Using the hardcoded `'triggered-message'` means all messages without an ID become indistinguishable in logs and for any consumers relying on `message.id`. Consider either propagating `undefined` and requiring consumers to handle it, or deriving a more descriptive ID from available metadata to keep messages distinguishable for diagnostics and any ID-based logic.
Suggested implementation:
```typescript
function toQueueMessage<TPayload>(triggeredMessage: TriggeredQueueMessage<TPayload>): QueueMessage<TPayload> {
return {
...(triggeredMessage.id !== undefined ? { id: triggeredMessage.id } : {}),
payload: triggeredMessage.payload,
...(triggeredMessage.popReceipt !== undefined ? { popReceipt: triggeredMessage.popReceipt } : {}),
...(triggeredMessage.dequeueCount !== undefined ? { dequeueCount: triggeredMessage.dequeueCount } : {}),
};
}
```
If the `QueueMessage<TPayload>` type currently requires a non-optional `id: string`, you should:
1. Update `QueueMessage` so that `id` is optional (e.g. `id?: string`) to reflect that some triggered messages legitimately do not have an ID.
2. Audit any consumers of `QueueMessage` that assume `message.id` is always present, and add appropriate handling (e.g. conditional logic or fallback generation localized to where it's needed) instead of relying on a synthetic constant ID.
</issue_to_address>
### Comment 2
<location path="apps/api/src/cellix.ts" line_range="172-173" />
<code_context>
};
-type AppHost<AppServices> = RequestScopedHost<AppServices, unknown>;
+type AppHost<AppServices> = RequestScopedHost<AppServices, unknown> & {
+ forSystem?(): Promise<AppServices>;
+};
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Making `forSystem` optional on `AppHost` but required by some handlers may hide configuration mistakes until runtime.
Queue handlers such as `communityUpdateQueueHandlerCreator` require a system-scoped host and will throw if `forSystem` is missing. By typing `forSystem` as optional on `AppHost`, misconfigured application-service factories will only fail at runtime. Consider either introducing a separate type (e.g. `SystemCapableAppHost`) or requiring `forSystem` on the host used to wire queue handlers, so the type system enforces this contract instead of relying on runtime checks.
Suggested implementation:
```typescript
options: Omit<StorageQueueFunctionOptions<TMessage>, 'handler'>,
handlerCreator: (applicationServicesHost: SystemCapableAppHost<AppServices>, infrastructureRegistry: InitializedServiceRegistry) => StorageQueueHandler<TMessage>,
): AzureFunctionHandlerRegistry<ContextType, AppServices>;
```
1. If there are other queue (or system-scoped) handler creator signatures in this file that currently accept `AppHost<AppServices>` but require `forSystem`, update them to accept `SystemCapableAppHost<AppServices>` instead.
2. If any handlers in other files rely on a system-scoped host and currently type their parameter as `AppHost<AppServices>`, consider updating those to `SystemCapableAppHost<AppServices>` so the requirement is enforced at compile time.
</issue_to_address>
### Comment 3
<location path="packages/ocom/handler-queue-community-update/src/handler.ts" line_range="42-51" />
<code_context>
+ };
+};
+
+function buildTriggeredQueueMessage(message: CommunityUpdateMessage, invocationContext: InvocationContext) {
+ const triggeredMessage: {
+ payload: CommunityUpdateMessage;
+ id?: string;
+ popReceipt?: string;
+ dequeueCount?: number;
+ } = {
+ payload: message,
+ };
+
+ const id = pickString(invocationContext, ['id', 'messageId']);
+ if (id !== undefined) {
+ triggeredMessage.id = id;
+ }
+
+ const popReceipt = pickString(invocationContext, ['popReceipt']);
+ if (popReceipt !== undefined) {
+ triggeredMessage.popReceipt = popReceipt;
+ }
+
+ const dequeueCount = pickNumber(invocationContext, ['dequeueCount']);
+ if (dequeueCount !== undefined) {
+ triggeredMessage.dequeueCount = dequeueCount;
+ }
+
+ return triggeredMessage;
+}
+
</code_context>
<issue_to_address>
**suggestion:** `buildTriggeredQueueMessage` manually re-specifies the trigger message shape instead of reusing the shared `TriggeredQueueMessage` type.
This helper reconstructs the `{ payload, id?, popReceipt?, dequeueCount? }` shape even though `TriggeredQueueMessage<T>` exists in `queue-storage`. Returning `TriggeredQueueMessage<CommunityUpdateMessage>` (and typing `triggeredMessage` as such) would remove duplication and keep this code aligned with the shared contract as it evolves.
Suggested implementation:
```typescript
function buildTriggeredQueueMessage(
message: CommunityUpdateMessage,
invocationContext: InvocationContext,
): TriggeredQueueMessage<CommunityUpdateMessage> {
const triggeredMessage: TriggeredQueueMessage<CommunityUpdateMessage> = {
payload: message,
};
```
1. Ensure `TriggeredQueueMessage` is imported at the top of `packages/ocom/handler-queue-community-update/src/handler.ts`, for example:
`import { TriggeredQueueMessage } from 'queue-storage';`
or from whatever the correct local package path is in this repo (e.g. `@tribe/queue-storage` or similar), matching existing usage elsewhere.
2. No changes are needed at the callsite: `buildTriggeredQueueMessage(message, invocationContext)` already matches the updated signature.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| function toQueueMessage<TPayload>(triggeredMessage: TriggeredQueueMessage<TPayload>): QueueMessage<TPayload> { | ||
| return { | ||
| id: triggeredMessage.id ?? 'triggered-message', |
There was a problem hiding this comment.
suggestion (bug_risk): Defaulting the triggered message ID to a synthetic constant may make diagnosing issues harder.
Using the hardcoded 'triggered-message' means all messages without an ID become indistinguishable in logs and for any consumers relying on message.id. Consider either propagating undefined and requiring consumers to handle it, or deriving a more descriptive ID from available metadata to keep messages distinguishable for diagnostics and any ID-based logic.
Suggested implementation:
function toQueueMessage<TPayload>(triggeredMessage: TriggeredQueueMessage<TPayload>): QueueMessage<TPayload> {
return {
...(triggeredMessage.id !== undefined ? { id: triggeredMessage.id } : {}),
payload: triggeredMessage.payload,
...(triggeredMessage.popReceipt !== undefined ? { popReceipt: triggeredMessage.popReceipt } : {}),
...(triggeredMessage.dequeueCount !== undefined ? { dequeueCount: triggeredMessage.dequeueCount } : {}),
};
}If the QueueMessage<TPayload> type currently requires a non-optional id: string, you should:
- Update
QueueMessageso thatidis optional (e.g.id?: string) to reflect that some triggered messages legitimately do not have an ID. - Audit any consumers of
QueueMessagethat assumemessage.idis always present, and add appropriate handling (e.g. conditional logic or fallback generation localized to where it's needed) instead of relying on a synthetic constant ID.
| type AppHost<AppServices> = RequestScopedHost<AppServices, unknown> & { | ||
| forSystem?(): Promise<AppServices>; |
There was a problem hiding this comment.
suggestion (bug_risk): Making forSystem optional on AppHost but required by some handlers may hide configuration mistakes until runtime.
Queue handlers such as communityUpdateQueueHandlerCreator require a system-scoped host and will throw if forSystem is missing. By typing forSystem as optional on AppHost, misconfigured application-service factories will only fail at runtime. Consider either introducing a separate type (e.g. SystemCapableAppHost) or requiring forSystem on the host used to wire queue handlers, so the type system enforces this contract instead of relying on runtime checks.
Suggested implementation:
options: Omit<StorageQueueFunctionOptions<TMessage>, 'handler'>,
handlerCreator: (applicationServicesHost: SystemCapableAppHost<AppServices>, infrastructureRegistry: InitializedServiceRegistry) => StorageQueueHandler<TMessage>,
): AzureFunctionHandlerRegistry<ContextType, AppServices>;- If there are other queue (or system-scoped) handler creator signatures in this file that currently accept
AppHost<AppServices>but requireforSystem, update them to acceptSystemCapableAppHost<AppServices>instead. - If any handlers in other files rely on a system-scoped host and currently type their parameter as
AppHost<AppServices>, consider updating those toSystemCapableAppHost<AppServices>so the requirement is enforced at compile time.
| function buildTriggeredQueueMessage(message: CommunityUpdateMessage, invocationContext: InvocationContext) { | ||
| const triggeredMessage: { | ||
| payload: CommunityUpdateMessage; | ||
| id?: string; | ||
| popReceipt?: string; | ||
| dequeueCount?: number; | ||
| } = { | ||
| payload: message, | ||
| }; | ||
|
|
There was a problem hiding this comment.
suggestion: buildTriggeredQueueMessage manually re-specifies the trigger message shape instead of reusing the shared TriggeredQueueMessage type.
This helper reconstructs the { payload, id?, popReceipt?, dequeueCount? } shape even though TriggeredQueueMessage<T> exists in queue-storage. Returning TriggeredQueueMessage<CommunityUpdateMessage> (and typing triggeredMessage as such) would remove duplication and keep this code aligned with the shared contract as it evolves.
Suggested implementation:
function buildTriggeredQueueMessage(
message: CommunityUpdateMessage,
invocationContext: InvocationContext,
): TriggeredQueueMessage<CommunityUpdateMessage> {
const triggeredMessage: TriggeredQueueMessage<CommunityUpdateMessage> = {
payload: message,
};-
Ensure
TriggeredQueueMessageis imported at the top ofpackages/ocom/handler-queue-community-update/src/handler.ts, for example:import { TriggeredQueueMessage } from 'queue-storage';or from whatever the correct local package path is in this repo (e.g.
@tribe/queue-storageor similar), matching existing usage elsewhere. -
No changes are needed at the callsite:
buildTriggeredQueueMessage(message, invocationContext)already matches the updated signature.
Summary
Testing
Notes
Summary by Sourcery
Add Azure Storage Queue trigger support to the Cellix bootstrap API and wire up a community-update queue handler into the API app.
New Features:
Enhancements:
Documentation:
Tests: