@boostercloud/framework-provider-azure 3.4.4-debug.2 → 4.0.0

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.
@@ -36,14 +36,6 @@ async function search(cosmosDb, config, containerName, filters, limit, afterCurs
36
36
  // - Legacy cursors: Numeric cursor.id values must use OFFSET/LIMIT for backward compatibility
37
37
  const canUseContinuationToken = !isDistinctQuery;
38
38
  const hasLegacyCursor = typeof (afterCursor === null || afterCursor === void 0 ? void 0 : afterCursor.id) === 'string' && /^\d+$/.test(afterCursor.id);
39
- logger.debug('PAGINATION DEBUG - Initial analysis:', {
40
- isDistinctQuery,
41
- canUseContinuationToken,
42
- hasLegacyCursor,
43
- afterCursor,
44
- limit,
45
- finalQuery,
46
- });
47
39
  // Use Cosmos DB's continuation token pagination
48
40
  const feedOptions = {};
49
41
  if (limit) {
@@ -52,10 +44,6 @@ async function search(cosmosDb, config, containerName, filters, limit, afterCurs
52
44
  // Extract continuation token from the cursor (backward compatibility)
53
45
  if (afterCursor === null || afterCursor === void 0 ? void 0 : afterCursor.continuationToken) {
54
46
  feedOptions.continuationToken = afterCursor.continuationToken;
55
- logger.debug('PAGINATION DEBUG - Using provided continuation token:', {
56
- continuationToken: afterCursor.continuationToken,
57
- feedOptions,
58
- });
59
47
  }
60
48
  else if (!canUseContinuationToken || hasLegacyCursor) {
61
49
  // Legacy cursor format - fallback to OFFSET for backward compatibility
@@ -65,19 +53,8 @@ async function search(cosmosDb, config, containerName, filters, limit, afterCurs
65
53
  legacyQuery += ` LIMIT ${limit} `;
66
54
  }
67
55
  const legacyQuerySpec = { ...querySpec, query: legacyQuery };
68
- logger.debug('PAGINATION DEBUG - Using LEGACY OFFSET/LIMIT approach:', {
69
- reason: !canUseContinuationToken ? 'DISTINCT query detected' : 'Legacy numeric cursor detected',
70
- offset,
71
- legacyQuery,
72
- legacyQuerySpec,
73
- });
74
56
  const { resources } = await container.items.query(legacyQuerySpec).fetchAll();
75
57
  const processedResources = processResources(resources);
76
- logger.debug('PAGINATION DEBUG - Legacy query results:', {
77
- resourcesCount: (resources === null || resources === void 0 ? void 0 : resources.length) || 0,
78
- processedResourcesCount: processedResources.length,
79
- nextCursor: { id: (offset + processedResources.length).toString() },
80
- });
81
58
  return {
82
59
  items: processedResources !== null && processedResources !== void 0 ? processedResources : [],
83
60
  count: processedResources.length,
@@ -86,35 +63,17 @@ async function search(cosmosDb, config, containerName, filters, limit, afterCurs
86
63
  },
87
64
  };
88
65
  }
89
- logger.debug('PAGINATION DEBUG - About to execute continuation token query with feedOptions:', feedOptions);
90
66
  const queryIterator = container.items.query(querySpec, feedOptions);
91
67
  const { resources, continuationToken } = await queryIterator.fetchNext();
92
- logger.debug('PAGINATION DEBUG - Cosmos SDK response:', {
93
- resourcesCount: (resources === null || resources === void 0 ? void 0 : resources.length) || 0,
94
- continuationTokenReceived: !!continuationToken,
95
- continuationTokenValue: continuationToken,
96
- continuationTokenType: typeof continuationToken,
97
- continuationTokenLength: continuationToken === null || continuationToken === void 0 ? void 0 : continuationToken.length,
98
- });
99
68
  const finalResources = processResources(resources || []);
100
69
  let cursor;
101
70
  if (continuationToken) {
102
71
  cursor = { continuationToken };
103
- logger.debug('PAGINATION DEBUG - Setting cursor with continuation token:', cursor);
104
72
  }
105
73
  else if (finalResources.length > 0) {
106
74
  const currentOffset = (afterCursor === null || afterCursor === void 0 ? void 0 : afterCursor.id) && !isNaN(parseInt(afterCursor.id)) ? parseInt(afterCursor.id) : 0;
107
- cursor = { id: (currentOffset + finalResources.length).toString() };
108
- logger.debug('PAGINATION DEBUG - No continuation token, setting fallback cursor:', cursor);
75
+ cursor = { id: (currentOffset + finalResources.length).toString() }; // Use the length of the results to calculate the next id
109
76
  }
110
- else {
111
- logger.debug('PAGINATION DEBUG - No continuation token and no resources, no cursor set');
112
- }
113
- logger.debug('PAGINATION DEBUG - Final response:', {
114
- itemsCount: finalResources.length,
115
- cursor,
116
- hasMoreResults: !!continuationToken,
117
- });
118
77
  return {
119
78
  items: finalResources,
120
79
  count: finalResources.length,
package/dist/index.d.ts CHANGED
@@ -2,3 +2,4 @@ import { HasInfrastructure, ProviderLibrary, RocketDescriptor } from '@boostercl
2
2
  export declare function loadInfrastructurePackage(packageName: string): HasInfrastructure;
3
3
  export declare const Provider: (rockets?: RocketDescriptor[]) => ProviderLibrary;
4
4
  export * from './constants';
5
+ export * from './types/azure-func-types';
package/dist/index.js CHANGED
@@ -166,3 +166,4 @@ const Provider = (rockets) => ({
166
166
  });
167
167
  exports.Provider = Provider;
168
168
  tslib_1.__exportStar(require("./constants"), exports);
169
+ tslib_1.__exportStar(require("./types/azure-func-types"), exports);
@@ -1,14 +1,23 @@
1
- import { Cookie } from '@azure/functions';
1
+ import { HttpResponseInit } from '@azure/functions';
2
2
  /**
3
- * See https://docs.microsoft.com/es-es/azure/azure-functions/functions-reference-node#response-object
3
+ * Standard HTTP response type for Azure Functions v4.
4
+ * Uses HttpResponseInit from '@azure/functions' v4.
4
5
  */
5
- export interface ContextResponse {
6
- body: string;
7
- headers: object;
8
- isRaw?: boolean;
9
- status: number;
10
- cookies?: Cookie[];
6
+ export type AzureHttpResponse = HttpResponseInit;
7
+ /**
8
+ * Web PubSub response types for Azure Functions v4.
9
+ * These are returned directly from Web PubSub triggered functions.
10
+ */
11
+ export interface WebPubSubConnectResponse {
12
+ subprotocol?: string;
13
+ userId?: string;
14
+ groups?: string[];
15
+ roles?: string[];
16
+ }
17
+ export interface WebPubSubMessageResponse {
18
+ data?: unknown;
19
+ dataType?: 'json' | 'text' | 'binary';
11
20
  }
12
- export declare function requestSucceeded(body?: unknown, headers?: Record<string, number | string | ReadonlyArray<string>>): Promise<ContextResponse | void>;
13
- export declare function requestFailed(error: Error): Promise<ContextResponse>;
14
- export declare function healthRequestResult(body: unknown, isHealthy: boolean): Promise<ContextResponse>;
21
+ export declare function requestSucceeded(body?: unknown, headers?: Record<string, number | string | ReadonlyArray<string>>): Promise<AzureHttpResponse | WebPubSubConnectResponse | WebPubSubMessageResponse | void>;
22
+ export declare function requestFailed(error: Error): Promise<AzureHttpResponse>;
23
+ export declare function healthRequestResult(body: unknown, isHealthy: boolean): Promise<AzureHttpResponse>;
@@ -5,25 +5,51 @@ exports.requestFailed = requestFailed;
5
5
  exports.healthRequestResult = healthRequestResult;
6
6
  const framework_types_1 = require("@boostercloud/framework-types");
7
7
  const WEB_SOCKET_PROTOCOL_HEADER = 'Sec-WebSocket-Protocol';
8
+ const WEB_SOCKET_MESSAGE_MARKER_HEADER = 'X-Booster-WebSocket-Message';
8
9
  async function requestSucceeded(body, headers) {
10
+ // Check if this is a Web Pubsub CONNECT event (has the specific WebSocket header)
11
+ const isWebSocketConnect = headers && Object.keys(headers).includes(WEB_SOCKET_PROTOCOL_HEADER);
12
+ // Web PubSub CONNECT event - return subprotocol response format
13
+ if (isWebSocketConnect) {
14
+ const subprotocol = headers[WEB_SOCKET_PROTOCOL_HEADER];
15
+ return {
16
+ subprotocol: Array.isArray(subprotocol) ? subprotocol[0] : String(subprotocol),
17
+ };
18
+ }
19
+ // Check if this is a Web Pubsub MESSAGE event (has the marker header from framework-core
20
+ const isWebSocketMessage = headers && headers[WEB_SOCKET_MESSAGE_MARKER_HEADER] === 'true';
21
+ // Web PubSub MESSAGE event - return data response format
22
+ if (isWebSocketMessage) {
23
+ return {
24
+ data: body,
25
+ dataType: 'json',
26
+ };
27
+ }
28
+ // No body and no meaningful headers - nothing to return (e.g., DISCONNECT)
9
29
  if (!body && (!headers || Object.keys(headers).length === 0)) {
10
30
  return;
11
31
  }
12
- const isWebSocket = headers && Object.keys(headers).includes(WEB_SOCKET_PROTOCOL_HEADER);
13
- let extraParams = {};
14
- if (isWebSocket) {
15
- extraParams = { Subprotocol: headers[WEB_SOCKET_PROTOCOL_HEADER] };
32
+ // Standard HTTP response for everything else
33
+ const responseHeaders = {
34
+ 'Access-Control-Allow-Origin': '*',
35
+ 'Content-Type': 'application/json',
36
+ };
37
+ if (headers) {
38
+ for (const [key, value] of Object.entries(headers)) {
39
+ // Don't include internal Booster headers in HTTP response
40
+ if (key.startsWith('X-Booster-'))
41
+ continue;
42
+ responseHeaders[key] = Array.isArray(value) ? value.join(',') : String(value);
43
+ }
16
44
  }
17
- return {
18
- headers: {
19
- 'Access-Control-Allow-Origin': '*',
20
- 'Content-Type': 'application/json',
21
- ...headers,
22
- },
45
+ const response = {
46
+ headers: responseHeaders,
23
47
  status: 200,
24
- body: body ? JSON.stringify(body) : '',
25
- ...extraParams,
26
48
  };
49
+ if (body) {
50
+ response.jsonBody = body;
51
+ }
52
+ return response;
27
53
  }
28
54
  async function requestFailed(error) {
29
55
  const status = (0, framework_types_1.httpStatusCodeFor)(error);
@@ -33,11 +59,11 @@ async function requestFailed(error) {
33
59
  'Content-Type': 'application/json',
34
60
  },
35
61
  status,
36
- body: JSON.stringify({
62
+ jsonBody: {
37
63
  status,
38
64
  title: (0, framework_types_1.toClassTitle)(error),
39
65
  reason: error.message,
40
- }),
66
+ },
41
67
  };
42
68
  }
43
69
  async function healthRequestResult(body, isHealthy) {
@@ -47,6 +73,6 @@ async function healthRequestResult(body, isHealthy) {
47
73
  'Content-Type': 'application/json',
48
74
  },
49
75
  status: isHealthy ? 200 : 503,
50
- body: JSON.stringify(body),
76
+ jsonBody: body,
51
77
  };
52
78
  }
@@ -1,7 +1,6 @@
1
1
  import { CosmosClient } from '@azure/cosmos';
2
2
  import { BoosterConfig, EntitySnapshotEnvelope, EventEnvelope, NonPersistedEntitySnapshotEnvelope, UUID } from '@boostercloud/framework-types';
3
- import { Context } from '@azure/functions';
4
- export declare function rawEventsToEnvelopes(context: Context): Array<EventEnvelope>;
3
+ export declare function rawEventsToEnvelopes(rawInput: unknown): Array<EventEnvelope>;
5
4
  export declare function readEntityEventsSince(cosmosDb: CosmosClient, config: BoosterConfig, entityTypeName: string, entityID: UUID, since?: string): Promise<Array<EventEnvelope>>;
6
5
  export declare function readEntityLatestSnapshot(cosmosDb: CosmosClient, config: BoosterConfig, entityTypeName: string, entityID: UUID): Promise<EntitySnapshotEnvelope | undefined>;
7
6
  export declare function storeSnapshot(cosmosDb: CosmosClient, snapshotEnvelope: NonPersistedEntitySnapshotEnvelope, config: BoosterConfig): Promise<EntitySnapshotEnvelope>;
@@ -8,9 +8,17 @@ exports.storeDispatchedEvent = storeDispatchedEvent;
8
8
  const framework_common_helpers_1 = require("@boostercloud/framework-common-helpers");
9
9
  const constants_1 = require("../constants");
10
10
  const partition_keys_1 = require("./partition-keys");
11
+ const azure_func_types_1 = require("../types/azure-func-types");
11
12
  const originOfTime = new Date(0).toISOString();
12
- function rawEventsToEnvelopes(context) {
13
- return context.bindings.rawEvent;
13
+ function rawEventsToEnvelopes(rawInput) {
14
+ if ((0, azure_func_types_1.isCosmosDBFunctionInput)(rawInput)) {
15
+ return rawInput.documents;
16
+ }
17
+ // Fallback for direct array input (used in some test scenarios)
18
+ if (Array.isArray(rawInput)) {
19
+ return rawInput;
20
+ }
21
+ throw new Error('Invalid input type for rawEventsToEnvelopes: expected AzureCosmosDBFunctionInput or Array');
14
22
  }
15
23
  async function readEntityEventsSince(cosmosDb, config, entityTypeName, entityID, since) {
16
24
  const logger = (0, framework_common_helpers_1.getLogger)(config, 'events-adapter#readEntityEventsSince');
@@ -1,5 +1,34 @@
1
1
  import { BoosterConfig, EventEnvelope, EventStream } from '@boostercloud/framework-types';
2
- import { Context } from '@azure/functions';
3
2
  import { CosmosClient } from '@azure/cosmos';
4
- export declare function dedupEventStream(cosmosDb: CosmosClient, config: BoosterConfig, context: Context): Promise<EventStream>;
5
- export declare function rawEventsStreamToEnvelopes(config: BoosterConfig, context: Context, dedupEventStream: EventStream): Array<EventEnvelope>;
3
+ import { AzureEventHubFunctionInput } from '../types/azure-func-types';
4
+ /**
5
+ * V4 Programming Model: Input wrapper for Event Hub consumer functions.
6
+ * Extends the base type with binding data for event metadata.
7
+ */
8
+ export interface EventHubConsumerInput extends AzureEventHubFunctionInput {
9
+ /** Binding data containing Event Hub metadata arrays */
10
+ bindingData: {
11
+ partitionKeyArray: string[];
12
+ offsetArray: string[];
13
+ sequenceNumberArray: number[];
14
+ enqueuedTimeUtcArray: string[];
15
+ };
16
+ }
17
+ /**
18
+ * Deduplicates events from the Event Hub stream using Cosmos DB.
19
+ * In v4 programming model, messages are passed in the input wrapper.
20
+ * @param cosmosDb - The Cosmos DB client
21
+ * @param config - The Booster configuration
22
+ * @param rawInput - The raw input from the Azure Function, expected to be of type EventHubConsumerInput
23
+ * @returns A promise that resolves to the deduplicated event stream
24
+ */
25
+ export declare function dedupEventStream(cosmosDb: CosmosClient, config: BoosterConfig, rawInput: unknown): Promise<EventStream>;
26
+ /**
27
+ * Converts raw Event Hub events to Booster EventEnvelope format.
28
+ * In v4 programming model, binding data is passed via triggerMetadata on the context.
29
+ * @param config - Booster configuration
30
+ * @param rawInput - The raw input from the Azure Function
31
+ * @param dedupEventStream - The deduplicated event stream
32
+ * @returns An array of EventEnvelope objects
33
+ */
34
+ export declare function rawEventsStreamToEnvelopes(config: BoosterConfig, rawInput: unknown, dedupEventStream: EventStream): Array<EventEnvelope>;
@@ -4,15 +4,32 @@ exports.dedupEventStream = dedupEventStream;
4
4
  exports.rawEventsStreamToEnvelopes = rawEventsStreamToEnvelopes;
5
5
  const framework_common_helpers_1 = require("@boostercloud/framework-common-helpers");
6
6
  const constants_1 = require("../constants");
7
+ const azure_func_types_1 = require("../types/azure-func-types");
7
8
  const DEFAULT_DEDUP_TTL = 86400;
8
- async function dedupEventStream(cosmosDb, config, context) {
9
+ /**
10
+ * Deduplicates events from the Event Hub stream using Cosmos DB.
11
+ * In v4 programming model, messages are passed in the input wrapper.
12
+ * @param cosmosDb - The Cosmos DB client
13
+ * @param config - The Booster configuration
14
+ * @param rawInput - The raw input from the Azure Function, expected to be of type EventHubConsumerInput
15
+ * @returns A promise that resolves to the deduplicated event stream
16
+ */
17
+ async function dedupEventStream(cosmosDb, config, rawInput) {
9
18
  var _a, _b;
10
19
  const logger = (0, framework_common_helpers_1.getLogger)(config, 'dedup-events-stream#dedupEventsStream');
11
- const events = context.bindings.eventHubMessages || [];
20
+ let events = [];
21
+ if ((0, azure_func_types_1.isEventHubFunctionInput)(rawInput)) {
22
+ events = rawInput.messages;
23
+ }
24
+ else if (Array.isArray(rawInput)) {
25
+ // Direct array for testing
26
+ events = rawInput;
27
+ }
12
28
  logger.debug(`Dedup ${events.length} events`);
13
29
  const resources = [];
14
30
  for (const event of events) {
15
- const rawParsed = JSON.parse(event);
31
+ // In v4, Event Hub messages may already be parsed objects, not JSON strings
32
+ const rawParsed = typeof event === 'string' ? JSON.parse(event) : event;
16
33
  const eventTag = {
17
34
  primaryKey: rawParsed._etag,
18
35
  createdAt: new Date().toISOString(),
@@ -31,23 +48,49 @@ async function dedupEventStream(cosmosDb, config, context) {
31
48
  if (error.code !== constants_1.AZURE_CONFLICT_ERROR_CODE) {
32
49
  throw error;
33
50
  }
34
- logger.warn(`Ignoring duplicated event with etag ${eventTag}.`);
51
+ logger.warn(`Ignoring duplicated event with etag ${eventTag.primaryKey}.`);
35
52
  }
36
53
  }
37
54
  return resources;
38
55
  }
39
- function rawEventsStreamToEnvelopes(config, context, dedupEventStream) {
56
+ /**
57
+ * Converts raw Event Hub events to Booster EventEnvelope format.
58
+ * In v4 programming model, binding data is passed via triggerMetadata on the context.
59
+ * @param config - Booster configuration
60
+ * @param rawInput - The raw input from the Azure Function
61
+ * @param dedupEventStream - The deduplicated event stream
62
+ * @returns An array of EventEnvelope objects
63
+ */
64
+ function rawEventsStreamToEnvelopes(config, rawInput, dedupEventStream) {
40
65
  const logger = (0, framework_common_helpers_1.getLogger)(config, 'events-adapter#rawEventsStreamToEnvelopes');
41
66
  logger.debug(`Mapping ${dedupEventStream.length} events`);
42
- const bindingData = context.bindingData;
67
+ // Extract binding data from the input
68
+ let bindingData;
69
+ if (isEventHubConsumerInput(rawInput)) {
70
+ bindingData = rawInput.bindingData;
71
+ }
43
72
  return dedupEventStream.map((message, index) => {
44
- const rawParsed = JSON.parse(message);
73
+ // In v4, Event Hub messages may already be parsed objects, not JSON strings
74
+ const rawParsed = typeof message === 'string' ? JSON.parse(message) : message;
45
75
  const instance = process.env.WEBSITE_INSTANCE_ID;
46
- const partitionKeyArrayElement = bindingData.partitionKeyArray[index];
47
- const offset = bindingData.offsetArray[index];
48
- const sequence = bindingData.sequenceNumberArray[index];
49
- const time = bindingData.enqueuedTimeUtcArray[index];
50
- logger.debug(`CONSUMED_EVENT:${instance}#${partitionKeyArrayElement}=>(${index}#${offset}#${sequence}#${time})`);
76
+ if (bindingData) {
77
+ const partitionKeyArrayElement = bindingData.partitionKeyArray[index];
78
+ const offset = bindingData.offsetArray[index];
79
+ const sequence = bindingData.sequenceNumberArray[index];
80
+ const time = bindingData.enqueuedTimeUtcArray[index];
81
+ logger.debug(`CONSUMED_EVENT:${instance}#${partitionKeyArrayElement}=>(${index}#${offset}#${sequence}#${time})`);
82
+ }
51
83
  return rawParsed;
52
84
  });
53
85
  }
86
+ /**
87
+ * Type guard to check if the input is an Event Hub consumer input with binding data
88
+ * @param input - The input to check
89
+ * @returns True if the input is an Event Hub consumer input, false otherwise
90
+ */
91
+ function isEventHubConsumerInput(input) {
92
+ var _a;
93
+ return ((0, azure_func_types_1.isEventHubFunctionInput)(input) &&
94
+ typeof input.bindingData === 'object' &&
95
+ Array.isArray((_a = input.bindingData) === null || _a === void 0 ? void 0 : _a.partitionKeyArray));
96
+ }
@@ -1,3 +1,2 @@
1
1
  import { BoosterConfig, GraphQLRequestEnvelope, GraphQLRequestEnvelopeError } from '@boostercloud/framework-types';
2
- import { Context } from '@azure/functions';
3
- export declare function rawGraphQLRequestToEnvelope(config: BoosterConfig, context: Context): Promise<GraphQLRequestEnvelope | GraphQLRequestEnvelopeError>;
2
+ export declare function rawGraphQLRequestToEnvelope(config: BoosterConfig, rawRequest: unknown): Promise<GraphQLRequestEnvelope | GraphQLRequestEnvelopeError>;
@@ -2,36 +2,157 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.rawGraphQLRequestToEnvelope = rawGraphQLRequestToEnvelope;
4
4
  const framework_common_helpers_1 = require("@boostercloud/framework-common-helpers");
5
- async function rawGraphQLRequestToEnvelope(config, context) {
6
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
5
+ const azure_func_types_1 = require("../types/azure-func-types");
6
+ async function rawGraphQLRequestToEnvelope(config, rawRequest) {
7
7
  const logger = (0, framework_common_helpers_1.getLogger)(config, 'graphql-adapter#rawGraphQLRequestToEnvelope');
8
- logger.debug('Received GraphQL request: ', context.req);
9
- const requestID = context.executionContext.invocationId;
10
- const connectionContext = (_a = context.bindingData) === null || _a === void 0 ? void 0 : _a.connectionContext;
11
- const connectionID = connectionContext === null || connectionContext === void 0 ? void 0 : connectionContext.connectionId;
12
- const eventType = (connectionContext === null || connectionContext === void 0 ? void 0 : connectionContext.eventType) === undefined ? 'MESSAGE' : (_b = connectionContext === null || connectionContext === void 0 ? void 0 : connectionContext.eventName) === null || _b === void 0 ? void 0 : _b.toUpperCase();
8
+ // Handle HTTP requests (v4 programming model)
9
+ if ((0, azure_func_types_1.isHttpFunctionInput)(rawRequest)) {
10
+ return handleHttpRequest(config, rawRequest, logger);
11
+ }
12
+ // Handle WebSocket requests (Web PubSub v4 programming model)
13
+ if ((0, azure_func_types_1.isWebPubSubFunctionInput)(rawRequest)) {
14
+ return handleWebPubSubRequest(config, rawRequest, logger);
15
+ }
16
+ // Fallback error for unknown input types
17
+ const error = new Error('Unknown request type received by GraphQL adapter');
18
+ logger.error('Received unknown request type:', rawRequest);
19
+ return {
20
+ error,
21
+ requestID: 'unknown',
22
+ eventType: 'MESSAGE',
23
+ context: {
24
+ request: {
25
+ headers: {},
26
+ body: undefined,
27
+ },
28
+ rawContext: rawRequest,
29
+ },
30
+ };
31
+ }
32
+ async function handleHttpRequest(config, input, logger) {
33
+ const { request, context } = input;
34
+ logger.debug('Received GraphQL request: ', request.url);
35
+ const requestID = context.invocationId;
13
36
  try {
37
+ // Parse the request body
14
38
  let graphQLValue = undefined;
15
- if (((_c = context.bindings) === null || _c === void 0 ? void 0 : _c.data) || ((_d = context.req) === null || _d === void 0 ? void 0 : _d.body)) {
16
- graphQLValue = ((_e = context.bindings) === null || _e === void 0 ? void 0 : _e.data) || ((_f = context.req) === null || _f === void 0 ? void 0 : _f.body);
39
+ try {
40
+ graphQLValue = await request.json();
41
+ }
42
+ catch {
43
+ // Body might not be JSON, try text
44
+ const textBody = await request.text();
45
+ if (textBody) {
46
+ try {
47
+ graphQLValue = JSON.parse(textBody);
48
+ }
49
+ catch {
50
+ graphQLValue = textBody;
51
+ }
52
+ }
53
+ }
54
+ // Convert headers to a plain object
55
+ const headers = {};
56
+ request.headers.forEach((value, key) => {
57
+ headers[key] = value;
58
+ });
59
+ return {
60
+ requestID,
61
+ eventType: 'MESSAGE',
62
+ token: request.headers.get('authorization') || undefined,
63
+ value: graphQLValue,
64
+ context: {
65
+ request: {
66
+ headers,
67
+ body: graphQLValue,
68
+ },
69
+ rawContext: input,
70
+ },
71
+ };
72
+ }
73
+ catch (e) {
74
+ const error = e;
75
+ logger.error('Error processing GraphQL HTTP request: ', error);
76
+ // Convert headers to a plain object for error response
77
+ const headers = {};
78
+ request.headers.forEach((value, key) => {
79
+ headers[key] = value;
80
+ });
81
+ return {
82
+ error,
83
+ requestID,
84
+ eventType: 'MESSAGE',
85
+ context: {
86
+ request: {
87
+ headers,
88
+ body: undefined,
89
+ },
90
+ rawContext: input,
91
+ },
92
+ };
93
+ }
94
+ }
95
+ async function handleWebPubSubRequest(config, input, logger) {
96
+ var _a, _b;
97
+ const { request: webPubSubRequest, context } = input;
98
+ const requestID = context.invocationId;
99
+ // Get connection context from either location (request or triggerMetadata)
100
+ const connectionContext = (0, azure_func_types_1.getWebPubSubConnectionContext)(input);
101
+ if (!connectionContext) {
102
+ const error = new Error('WebPubSub connectionContext not found in request or triggerMetadata');
103
+ logger.error('Missing connectionContext. Input structure', JSON.stringify(input, null, 2));
104
+ return {
105
+ error,
106
+ requestID,
107
+ eventType: 'MESSAGE',
108
+ context: {
109
+ request: {
110
+ headers: {},
111
+ body: undefined,
112
+ },
113
+ rawContext: input,
114
+ },
115
+ };
116
+ }
117
+ logger.debug('Received GraphQL WebSocket request: ', connectionContext.eventName);
118
+ const connectionID = connectionContext.connectionId;
119
+ // Map Web PubSub event names to Booster EventType
120
+ let eventType = 'MESSAGE';
121
+ if (((_a = connectionContext.eventType) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === 'system') {
122
+ const eventName = (_b = connectionContext.eventName) === null || _b === void 0 ? void 0 : _b.toUpperCase();
123
+ if (eventName === 'CONNECT' || eventName === 'DISCONNECT') {
124
+ eventType = eventName;
125
+ }
126
+ }
127
+ try {
128
+ // Parse the data if it's a JSON string (Web PubSub sends data as string)
129
+ let messageData = webPubSubRequest === null || webPubSubRequest === void 0 ? void 0 : webPubSubRequest.data;
130
+ if (typeof messageData === 'string') {
131
+ try {
132
+ messageData = JSON.parse(messageData);
133
+ logger.debug('Parsed Web PubSub message data from string:', messageData);
134
+ }
135
+ catch {
136
+ logger.debug('WebPubSub data is not valid JSON, using as-is:', messageData);
137
+ }
17
138
  }
18
139
  return {
19
140
  requestID,
20
141
  connectionID,
21
142
  eventType,
22
- token: (_h = (_g = context.req) === null || _g === void 0 ? void 0 : _g.headers) === null || _h === void 0 ? void 0 : _h.authorization,
23
- value: graphQLValue,
143
+ value: messageData,
24
144
  context: {
25
145
  request: {
26
- headers: (_j = context.req) === null || _j === void 0 ? void 0 : _j.headers,
27
- body: (_k = context.req) === null || _k === void 0 ? void 0 : _k.body,
146
+ headers: {},
147
+ body: messageData,
28
148
  },
29
- rawContext: context,
149
+ rawContext: input,
30
150
  },
31
151
  };
32
152
  }
33
153
  catch (e) {
34
154
  const error = e;
155
+ logger.error('Error processing GraphQL WebSocket request: ', error);
35
156
  return {
36
157
  error,
37
158
  requestID,
@@ -39,10 +160,10 @@ async function rawGraphQLRequestToEnvelope(config, context) {
39
160
  eventType,
40
161
  context: {
41
162
  request: {
42
- headers: (_l = context.req) === null || _l === void 0 ? void 0 : _l.headers,
43
- body: (_m = context.req) === null || _m === void 0 ? void 0 : _m.body,
163
+ headers: {},
164
+ body: undefined,
44
165
  },
45
- rawContext: context,
166
+ rawContext: input,
46
167
  },
47
168
  };
48
169
  }
@@ -1,6 +1,5 @@
1
1
  import { BoosterConfig, HealthEnvelope } from '@boostercloud/framework-types';
2
2
  import { Container, CosmosClient } from '@azure/cosmos';
3
- import { Context } from '@azure/functions';
4
3
  export declare function databaseUrl(cosmosDb: CosmosClient, config: BoosterConfig): Promise<Array<string>>;
5
4
  export declare function getContainer(cosmosDb: CosmosClient, config: BoosterConfig, containerName: string): Container;
6
5
  export declare function isContainerUp(cosmosDb: CosmosClient, config: BoosterConfig, containerName: string): Promise<boolean>;
@@ -15,6 +14,13 @@ export declare function isRocketFunctionUp(rocketFunctionAppName: string): Promi
15
14
  export declare function areRocketFunctionsUp(): Promise<{
16
15
  [key: string]: boolean;
17
16
  }>;
18
- export declare function rawRequestToSensorHealthComponentPath(rawRequest: Context): string;
19
- export declare function rawRequestToSensorHealth(context: Context): HealthEnvelope;
17
+ export declare function rawRequestToSensorHealthComponentPath(rawRequest: unknown): string;
18
+ /**
19
+ * Converts the raw HTTP request to a HealthEnvelope.
20
+ * Note: Body is not parsed synchronously in v4 - it will be undefined.
21
+ * The health endpoint typically doesn't need the request body.
22
+ * @param rawRequest - The raw HTTP request from the Azure Function
23
+ * @returns A HealthEnvelope object
24
+ */
25
+ export declare function rawRequestToSensorHealth(rawRequest: unknown): HealthEnvelope;
20
26
  export declare function databaseReadModelsHealthDetails(cosmosDb: CosmosClient, config: BoosterConfig): Promise<unknown>;
@@ -17,6 +17,7 @@ exports.rawRequestToSensorHealth = rawRequestToSensorHealth;
17
17
  exports.databaseReadModelsHealthDetails = databaseReadModelsHealthDetails;
18
18
  const constants_1 = require("../constants");
19
19
  const framework_common_helpers_1 = require("@boostercloud/framework-common-helpers");
20
+ const azure_func_types_1 = require("../types/azure-func-types");
20
21
  async function databaseUrl(cosmosDb, config) {
21
22
  const database = cosmosDb.database(config.resourceNames.applicationStack);
22
23
  return [database.url];
@@ -117,25 +118,46 @@ async function areRocketFunctionsUp() {
117
118
  return results.reduce((acc, result) => ({ ...acc, ...result }), {});
118
119
  }
119
120
  function rawRequestToSensorHealthComponentPath(rawRequest) {
120
- var _a;
121
- const parameters = (_a = rawRequest.req) === null || _a === void 0 ? void 0 : _a.url.replace(/^.*sensor\/health\/?/, '');
122
- return parameters !== null && parameters !== void 0 ? parameters : '';
121
+ if ((0, azure_func_types_1.isHttpFunctionInput)(rawRequest)) {
122
+ const input = rawRequest;
123
+ const url = input.request.url;
124
+ const parameters = url.replace(/^.*sensor\/health\/?/, '');
125
+ return parameters !== null && parameters !== void 0 ? parameters : '';
126
+ }
127
+ return '';
123
128
  }
124
- function rawRequestToSensorHealth(context) {
125
- var _a, _b, _c, _d;
126
- const componentPath = rawRequestToSensorHealthComponentPath(context);
127
- const requestID = context.executionContext.invocationId;
129
+ /**
130
+ * Converts the raw HTTP request to a HealthEnvelope.
131
+ * Note: Body is not parsed synchronously in v4 - it will be undefined.
132
+ * The health endpoint typically doesn't need the request body.
133
+ * @param rawRequest - The raw HTTP request from the Azure Function
134
+ * @returns A HealthEnvelope object
135
+ */
136
+ function rawRequestToSensorHealth(rawRequest) {
137
+ var _a;
138
+ if (!(0, azure_func_types_1.isHttpFunctionInput)(rawRequest)) {
139
+ throw new Error('Invalid input type for rawRequestToSensorHealth: expected AzureHttpFunctionInput');
140
+ }
141
+ const input = rawRequest;
142
+ const { request, context } = input;
143
+ const componentPath = rawRequestToSensorHealthComponentPath(rawRequest);
144
+ const requestID = context.invocationId;
145
+ // Convert headers to a plain object
146
+ const headers = {};
147
+ request.headers.forEach((value, key) => {
148
+ headers[key] = value;
149
+ });
128
150
  return {
129
151
  requestID: requestID,
130
152
  context: {
131
153
  request: {
132
- headers: (_a = context.req) === null || _a === void 0 ? void 0 : _a.headers,
133
- body: (_b = context.req) === null || _b === void 0 ? void 0 : _b.body,
154
+ headers,
155
+ body: undefined, // Body not parsed synchronously in v4
134
156
  },
135
- rawContext: context,
157
+ rawContext: rawRequest,
136
158
  },
137
159
  componentPath: componentPath,
138
- token: (_d = (_c = context.req) === null || _c === void 0 ? void 0 : _c.headers) === null || _d === void 0 ? void 0 : _d.authorization,
160
+ token: (_a = request.headers.get('authorization')) !== null && _a !== void 0 ? _a : undefined,
139
161
  };
140
162
  }
141
163
  async function databaseReadModelsHealthDetails(cosmosDb, config) {
@@ -3,4 +3,11 @@ import { BoosterConfig, ReadModelEnvelope, ReadModelInterface, ReadOnlyNonEmptyA
3
3
  export declare function fetchReadModel(db: CosmosClient, config: BoosterConfig, readModelName: string, readModelID: UUID): Promise<ReadOnlyNonEmptyArray<ReadModelInterface>>;
4
4
  export declare function storeReadModel(db: CosmosClient, config: BoosterConfig, readModelName: string, readModel: ReadModelInterface): Promise<void>;
5
5
  export declare function deleteReadModel(db: CosmosClient, config: BoosterConfig, readModelName: string, readModel: ReadModelInterface): Promise<void>;
6
+ /**
7
+ * Converts raw read model events from Cosmos DB change feed to Booster envelopes.
8
+ * In v4 programming model, the input wrapper contains documents directly and the typeName.
9
+ * @param config - Booster configuration
10
+ * @param rawEvents - Raw events from the Azure Function input
11
+ * @returns A promise that resolves to an array of ReadModelEnvelope objects
12
+ */
6
13
  export declare function rawReadModelEventsToEnvelopes(config: BoosterConfig, rawEvents: unknown): Promise<Array<ReadModelEnvelope>>;
@@ -7,6 +7,7 @@ exports.rawReadModelEventsToEnvelopes = rawReadModelEventsToEnvelopes;
7
7
  const framework_types_1 = require("@boostercloud/framework-types");
8
8
  const framework_common_helpers_1 = require("@boostercloud/framework-common-helpers");
9
9
  const constants_1 = require("../constants");
10
+ const subscription_model_1 = require("./subscription-model");
10
11
  async function fetchReadModel(db, config, readModelName, readModelID) {
11
12
  const logger = (0, framework_common_helpers_1.getLogger)(config, 'read-model-adapter#fetchReadModel');
12
13
  const { resource } = await db
@@ -100,12 +101,19 @@ async function deleteReadModel(db, config, readModelName, readModel) {
100
101
  logger.warn(`Read model to delete ${readModelName} ID = ${readModel.id} not found`);
101
102
  }
102
103
  }
104
+ /**
105
+ * Converts raw read model events from Cosmos DB change feed to Booster envelopes.
106
+ * In v4 programming model, the input wrapper contains documents directly and the typeName.
107
+ * @param config - Booster configuration
108
+ * @param rawEvents - Raw events from the Azure Function input
109
+ * @returns A promise that resolves to an array of ReadModelEnvelope objects
110
+ */
103
111
  async function rawReadModelEventsToEnvelopes(config, rawEvents) {
104
112
  const logger = (0, framework_common_helpers_1.getLogger)(config, 'read-model-adapter#rawReadModelEventsToEnvelopes');
105
113
  logger.debug(`Parsing raw read models ${JSON.stringify(rawEvents)}`);
106
- if (isSubscriptionContext(rawEvents)) {
107
- const typeName = rawEvents.executionContext.functionName.replace('-subscriptions-notifier', '');
108
- return rawEvents.bindings.rawEvent.map((rawEvent) => {
114
+ if ((0, subscription_model_1.isSubscriptionNotifierInput)(rawEvents)) {
115
+ const { documents, typeName } = rawEvents;
116
+ return documents.map((rawEvent) => {
109
117
  const { _rid, _self, _st, _etag, _lsn, _ts, ...rest } = rawEvent;
110
118
  return {
111
119
  typeName: typeName,
@@ -116,7 +124,3 @@ async function rawReadModelEventsToEnvelopes(config, rawEvents) {
116
124
  logger.warn(`Unexpected events to be parsed ${JSON.stringify(rawEvents)}`);
117
125
  return [];
118
126
  }
119
- function isSubscriptionContext(rawRequest) {
120
- return (rawRequest.bindings !== undefined &&
121
- rawRequest.bindings.rawEvent !== undefined);
122
- }
@@ -1,8 +1,24 @@
1
1
  import { BoosterConfig, ScheduledCommandEnvelope } from '@boostercloud/framework-types';
2
- interface AzureScheduledCommandEnvelope {
3
- bindings: {
4
- [name: string]: unknown;
5
- };
2
+ import { AzureTimerFunctionInput } from '../types/azure-func-types';
3
+ /**
4
+ * V4 Programming Model: Input wrapper for scheduled command functions.
5
+ * Extends the base timer input with the command type name.
6
+ */
7
+ export interface ScheduledCommandInput extends AzureTimerFunctionInput {
8
+ /** The scheduled command type name */
9
+ typeName: string;
6
10
  }
7
- export declare function rawScheduledInputToEnvelope(config: BoosterConfig, input: Partial<AzureScheduledCommandEnvelope>): Promise<ScheduledCommandEnvelope>;
8
- export {};
11
+ /**
12
+ * Type guard to check if the input is a scheduled command input
13
+ * @param input - The input to check
14
+ * @returns True if the input is a ScheduledCommandInput, false otherwise
15
+ */
16
+ export declare function isScheduledCommandInput(input: unknown): input is ScheduledCommandInput;
17
+ /**
18
+ * Converts the raw timer trigger input to a Booster ScheduledCommandEnvelope.
19
+ * In v4 programming model, the timer info and typeName are passed in the input wrapper.
20
+ * @param config - Booster configuration
21
+ * @param input - The raw input from the Azure Function
22
+ * @returns A promise that resolves to a ScheduledCommandEnvelope
23
+ */
24
+ export declare function rawScheduledInputToEnvelope(config: BoosterConfig, input: unknown): Promise<ScheduledCommandEnvelope>;
@@ -1,15 +1,35 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isScheduledCommandInput = isScheduledCommandInput;
3
4
  exports.rawScheduledInputToEnvelope = rawScheduledInputToEnvelope;
4
5
  const framework_types_1 = require("@boostercloud/framework-types");
5
6
  const framework_common_helpers_1 = require("@boostercloud/framework-common-helpers");
7
+ const azure_func_types_1 = require("../types/azure-func-types");
8
+ /**
9
+ * Type guard to check if the input is a scheduled command input
10
+ * @param input - The input to check
11
+ * @returns True if the input is a ScheduledCommandInput, false otherwise
12
+ */
13
+ function isScheduledCommandInput(input) {
14
+ return (0, azure_func_types_1.isTimerFunctionInput)(input);
15
+ }
16
+ /**
17
+ * Converts the raw timer trigger input to a Booster ScheduledCommandEnvelope.
18
+ * In v4 programming model, the timer info and typeName are passed in the input wrapper.
19
+ * @param config - Booster configuration
20
+ * @param input - The raw input from the Azure Function
21
+ * @returns A promise that resolves to a ScheduledCommandEnvelope
22
+ */
6
23
  async function rawScheduledInputToEnvelope(config, input) {
7
24
  const logger = (0, framework_common_helpers_1.getLogger)(config, 'scheduled-adapter#rawScheduledInputToEnvelope');
8
25
  logger.debug('Received AzureScheduledCommand request: ', input);
9
- if (!input.bindings || !Object.keys(input.bindings).length)
10
- throw new Error(`bindings is not defined or empty, scheduled command envelope should have the structure {bindings: [name: string]: string }, but you gave ${JSON.stringify(input)}`);
26
+ if (!isScheduledCommandInput(input)) {
27
+ throw new Error(`Invalid scheduled command input. Expected ScheduledCommandInput with typeName and timer, but received: ${JSON.stringify(input)}`);
28
+ }
29
+ const { typeName, timer } = input;
30
+ logger.debug(`Processing scheduled command: ${typeName}, isPastDue: ${timer.isPastDue}`);
11
31
  return {
12
32
  requestID: framework_types_1.UUID.generate(),
13
- typeName: Object.keys(input.bindings)[0],
33
+ typeName: typeName,
14
34
  };
15
35
  }
@@ -1,15 +1,8 @@
1
+ import { InvocationContext } from '@azure/functions';
1
2
  import { BoosterMetadata } from '@boostercloud/framework-types';
2
- export interface SubscriptionContext {
3
- invocationId: string;
4
- traceContext: unknown;
5
- executionContext: ExecutionContext;
6
- bindings: Bindings;
7
- bindingData: unknown;
8
- bindingDefinitions: unknown;
9
- }
10
- export interface Bindings {
11
- rawEvent: RawEvent[];
12
- }
3
+ /**
4
+ * Raw event structure from Cosmos DB change feed
5
+ */
13
6
  export interface RawEvent {
14
7
  id: string;
15
8
  _rid: string;
@@ -20,9 +13,19 @@ export interface RawEvent {
20
13
  boosterMetadata: BoosterMetadata;
21
14
  _lsn: number;
22
15
  }
23
- export interface ExecutionContext {
24
- invocationId: string;
25
- functionName: string;
26
- functionDirectory: string;
27
- retryContext: null;
16
+ /**
17
+ * V4 Programming Model: Input wrapper for subscription notifier functions.
18
+ * In v4, documents are passed directly as the first argument to the handler.
19
+ */
20
+ export interface SubscriptionNotifierInput {
21
+ documents: RawEvent[];
22
+ context: InvocationContext;
23
+ /** The read model type name, extracted from function configuration */
24
+ typeName: string;
28
25
  }
26
+ /**
27
+ * Type guard to check if the input is a v4 subscription notifier input
28
+ * @param input - The input to check
29
+ * @returns True if the input is a SubscriptionNotifierInput, false otherwise
30
+ */
31
+ export declare function isSubscriptionNotifierInput(input: unknown): input is SubscriptionNotifierInput;
@@ -1,2 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isSubscriptionNotifierInput = isSubscriptionNotifierInput;
4
+ /**
5
+ * Type guard to check if the input is a v4 subscription notifier input
6
+ * @param input - The input to check
7
+ * @returns True if the input is a SubscriptionNotifierInput, false otherwise
8
+ */
9
+ function isSubscriptionNotifierInput(input) {
10
+ return (typeof input === 'object' &&
11
+ input !== null &&
12
+ 'documents' in input &&
13
+ 'context' in input &&
14
+ 'typeName' in input &&
15
+ Array.isArray(input.documents));
16
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Azure Functions v4 Programming Model Types
3
+ *
4
+ * These types provide a wrapper for Azure Functions vs inputs to standardize
5
+ * the interface between function handlers and Booster adapters
6
+ */
7
+ import { HttpRequest, InvocationContext } from '@azure/functions';
8
+ /**
9
+ * Standard wrapper for HTTP-triggered Azure Function v4 inputs.
10
+ * This replaces the v3 Context object for HTTP triggers.
11
+ */
12
+ export interface AzureHttpFunctionInput {
13
+ request: HttpRequest;
14
+ context: InvocationContext;
15
+ }
16
+ /**
17
+ * Standard wrapper for CosmosDB-triggered Azure Function v4 inputs.
18
+ */
19
+ export interface AzureCosmosDBFunctionInput {
20
+ documents: unknown[];
21
+ context: InvocationContext;
22
+ }
23
+ /**
24
+ * Standard wrapper for EventHub-triggered Azure Function v4 inputs.
25
+ */
26
+ export interface AzureEventHubFunctionInput {
27
+ messages: unknown[];
28
+ context: InvocationContext;
29
+ }
30
+ /**
31
+ * Standard wrapper for Timer-triggered Azure Function v4 inputs.
32
+ */
33
+ export interface AzureTimerFunctionInput {
34
+ timer: {
35
+ isPastDue: boolean;
36
+ schedule: {
37
+ adjustForDST: boolean;
38
+ };
39
+ scheduleStatus?: {
40
+ last: string;
41
+ next: string;
42
+ lastUpdated: string;
43
+ };
44
+ };
45
+ context: InvocationContext;
46
+ }
47
+ /**
48
+ * Web PubSub connection context structure
49
+ */
50
+ export interface WebPubSubConnectionContext {
51
+ connectionId: string;
52
+ userId?: string;
53
+ hub: string;
54
+ eventType: string;
55
+ eventName: string;
56
+ }
57
+ /**
58
+ * Standard wrapper for Web PubSub-triggered Azure Function v4 inputs.
59
+ * Note: connectionContext can be found in either request.connectionContext
60
+ * or context.triggerMetadata.connectionContext depending on Azure Functions version.
61
+ */
62
+ export interface AzureWebPubSubFunctionInput {
63
+ request: {
64
+ connectionContext?: WebPubSubConnectionContext;
65
+ data?: unknown;
66
+ dataType?: string;
67
+ };
68
+ context: InvocationContext & {
69
+ triggerMetadata?: {
70
+ connectionContext?: WebPubSubConnectionContext;
71
+ };
72
+ };
73
+ }
74
+ /**
75
+ * Type alias for the raw request that Booster adapters receive.
76
+ * This can be any of the v4 input types.
77
+ */
78
+ export type AzureFunctionRawRequest = AzureHttpFunctionInput | AzureCosmosDBFunctionInput | AzureEventHubFunctionInput | AzureTimerFunctionInput | AzureWebPubSubFunctionInput;
79
+ /**
80
+ * Type guard to check if the input is an HTTP function input
81
+ * @param input - The input to check
82
+ * @returns True if the input is an HTTP function input, false otherwise
83
+ */
84
+ export declare function isHttpFunctionInput(input: unknown): input is AzureHttpFunctionInput;
85
+ /**
86
+ * Type guard to check if the input is a CosmosDB function input
87
+ * @param input - The input to check
88
+ * @returns True if the input is a CosmosDB function input, false otherwise
89
+ */
90
+ export declare function isCosmosDBFunctionInput(input: unknown): input is AzureCosmosDBFunctionInput;
91
+ /**
92
+ * Type guard to check if the input is an EventHub function input
93
+ * @param input - The input to check
94
+ * @returns True if the input is an EventHub function input, false otherwise
95
+ */
96
+ export declare function isEventHubFunctionInput(input: unknown): input is AzureEventHubFunctionInput;
97
+ /**
98
+ * Type guard to check if the input is a Timer function input
99
+ * @param input - The input to check
100
+ * @returns True if the input is a Timer function input, false otherwise
101
+ */
102
+ export declare function isTimerFunctionInput(input: unknown): input is AzureTimerFunctionInput;
103
+ /**
104
+ * Type guard to check if the input is a Web PubSub function input.
105
+ * Checks for connectionContext in both request.connectionContext and context.triggerMetadata.connectionContext
106
+ * as Azure Functions v4 may provide it in different locations.
107
+ * @param input - The input to check
108
+ * @returns True if the input is a Web PubSub function input, false otherwise
109
+ */
110
+ export declare function isWebPubSubFunctionInput(input: unknown): input is AzureWebPubSubFunctionInput;
111
+ /**
112
+ * Helper to extract connectionContext from either location in Web PubSub input
113
+ * @param input - The Azure Web PubSub function input
114
+ * @returns The connection context if present, undefined otherwise
115
+ */
116
+ export declare function getWebPubSubConnectionContext(input: AzureWebPubSubFunctionInput): WebPubSubConnectionContext | undefined;
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isHttpFunctionInput = isHttpFunctionInput;
4
+ exports.isCosmosDBFunctionInput = isCosmosDBFunctionInput;
5
+ exports.isEventHubFunctionInput = isEventHubFunctionInput;
6
+ exports.isTimerFunctionInput = isTimerFunctionInput;
7
+ exports.isWebPubSubFunctionInput = isWebPubSubFunctionInput;
8
+ exports.getWebPubSubConnectionContext = getWebPubSubConnectionContext;
9
+ /**
10
+ * Type guard to check if the input is an HTTP function input
11
+ * @param input - The input to check
12
+ * @returns True if the input is an HTTP function input, false otherwise
13
+ */
14
+ function isHttpFunctionInput(input) {
15
+ var _a;
16
+ return (typeof input === 'object' &&
17
+ input !== null &&
18
+ 'request' in input &&
19
+ 'context' in input &&
20
+ typeof ((_a = input.request) === null || _a === void 0 ? void 0 : _a.method) === 'string');
21
+ }
22
+ /**
23
+ * Type guard to check if the input is a CosmosDB function input
24
+ * @param input - The input to check
25
+ * @returns True if the input is a CosmosDB function input, false otherwise
26
+ */
27
+ function isCosmosDBFunctionInput(input) {
28
+ return (typeof input === 'object' &&
29
+ input !== null &&
30
+ 'documents' in input &&
31
+ 'context' in input &&
32
+ Array.isArray(input.documents));
33
+ }
34
+ /**
35
+ * Type guard to check if the input is an EventHub function input
36
+ * @param input - The input to check
37
+ * @returns True if the input is an EventHub function input, false otherwise
38
+ */
39
+ function isEventHubFunctionInput(input) {
40
+ return (typeof input === 'object' &&
41
+ input !== null &&
42
+ 'messages' in input &&
43
+ 'context' in input &&
44
+ Array.isArray(input.messages));
45
+ }
46
+ /**
47
+ * Type guard to check if the input is a Timer function input
48
+ * @param input - The input to check
49
+ * @returns True if the input is a Timer function input, false otherwise
50
+ */
51
+ function isTimerFunctionInput(input) {
52
+ var _a;
53
+ return (typeof input === 'object' &&
54
+ input !== null &&
55
+ 'timer' in input &&
56
+ 'context' in input &&
57
+ typeof ((_a = input.timer) === null || _a === void 0 ? void 0 : _a.isPastDue) === 'boolean');
58
+ }
59
+ /**
60
+ * Type guard to check if the input is a Web PubSub function input.
61
+ * Checks for connectionContext in both request.connectionContext and context.triggerMetadata.connectionContext
62
+ * as Azure Functions v4 may provide it in different locations.
63
+ * @param input - The input to check
64
+ * @returns True if the input is a Web PubSub function input, false otherwise
65
+ */
66
+ function isWebPubSubFunctionInput(input) {
67
+ var _a, _b, _c, _d, _e;
68
+ if (typeof input !== 'object' || input === null || !('request' in input) || !('context' in input)) {
69
+ return false;
70
+ }
71
+ const typedInput = input;
72
+ // Check connectionContext in request.connectionContext
73
+ if (typeof ((_b = (_a = typedInput.request) === null || _a === void 0 ? void 0 : _a.connectionContext) === null || _b === void 0 ? void 0 : _b.connectionId) === 'string') {
74
+ return true;
75
+ }
76
+ // Check connectionContext in context.triggerMetadata.connectionContext
77
+ return typeof ((_e = (_d = (_c = typedInput.context) === null || _c === void 0 ? void 0 : _c.triggerMetadata) === null || _d === void 0 ? void 0 : _d.connectionContext) === null || _e === void 0 ? void 0 : _e.connectionId) === 'string';
78
+ }
79
+ /**
80
+ * Helper to extract connectionContext from either location in Web PubSub input
81
+ * @param input - The Azure Web PubSub function input
82
+ * @returns The connection context if present, undefined otherwise
83
+ */
84
+ function getWebPubSubConnectionContext(input) {
85
+ var _a, _b, _c, _d;
86
+ return (_b = (_a = input.request) === null || _a === void 0 ? void 0 : _a.connectionContext) !== null && _b !== void 0 ? _b : (_d = (_c = input.context) === null || _c === void 0 ? void 0 : _c.triggerMetadata) === null || _d === void 0 ? void 0 : _d.connectionContext;
87
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boostercloud/framework-provider-azure",
3
- "version": "3.4.4-debug.2",
3
+ "version": "4.0.0",
4
4
  "description": "Handle Booster's integration with Azure",
5
5
  "keywords": [
6
6
  "framework-provider-azure"
@@ -20,21 +20,21 @@
20
20
  "url": "git+https://github.com/boostercloud/booster.git"
21
21
  },
22
22
  "engines": {
23
- "node": ">=20.0.0 <21.0.0"
23
+ "node": ">=22.0.0 <23.0.0"
24
24
  },
25
25
  "dependencies": {
26
26
  "@azure/cosmos": "^4.3.0",
27
- "@azure/functions": "^1.2.2",
27
+ "@azure/functions": "^4.0.0",
28
28
  "@azure/identity": "~4.7.0",
29
29
  "@azure/event-hubs": "5.11.1",
30
- "@boostercloud/framework-common-helpers": "workspace:^3.4.3",
31
- "@boostercloud/framework-types": "workspace:^3.4.3",
30
+ "@boostercloud/framework-common-helpers": "^4.0.0",
31
+ "@boostercloud/framework-types": "^4.0.0",
32
32
  "tslib": "^2.4.0",
33
33
  "@effect-ts/core": "^0.60.4",
34
34
  "@azure/web-pubsub": "~1.1.0"
35
35
  },
36
36
  "devDependencies": {
37
- "@boostercloud/eslint-config": "workspace:^3.4.3",
37
+ "@boostercloud/eslint-config": "^4.0.0",
38
38
  "@types/chai": "4.2.18",
39
39
  "@types/chai-as-promised": "7.1.4",
40
40
  "@types/faker": "5.1.5",
@@ -61,27 +61,16 @@
61
61
  "typescript": "5.7.3",
62
62
  "eslint-plugin-unicorn": "~44.0.2"
63
63
  },
64
+ "bugs": {
65
+ "url": "https://github.com/boostercloud/booster/issues"
66
+ },
64
67
  "scripts": {
65
68
  "format": "prettier --write --ext '.js,.ts' **/*.ts **/*/*.ts",
66
69
  "lint:check": "eslint --ext '.js,.ts' **/*.ts",
67
70
  "lint:fix": "eslint --quiet --fix --ext '.js,.ts' **/*.ts",
68
71
  "build": "tsc -b tsconfig.json",
69
72
  "clean": "rimraf ./dist tsconfig.tsbuildinfo",
70
- "prepack": "tsc -b tsconfig.json",
71
73
  "test:provider-azure": "npm run test",
72
74
  "test": "nyc --extension .ts mocha --forbid-only \"test/**/*.test.ts\""
73
- },
74
- "bugs": {
75
- "url": "https://github.com/boostercloud/booster/issues"
76
- },
77
- "pnpm": {
78
- "overrides": {
79
- "pac-resolver@<5.0.0": ">=5.0.0",
80
- "underscore@>=1.3.2 <1.12.1": ">=1.13.6",
81
- "node-fetch@<2.6.7": ">=2.6.7",
82
- "ws@>=7.0.0 <7.4.6": ">=7.4.6",
83
- "nanoid@>=3.0.0 <3.1.31": ">=3.1.31",
84
- "node-fetch@<2.6.1": ">=2.6.1"
85
- }
86
75
  }
87
- }
76
+ }