@boostercloud/framework-provider-azure 3.4.4-debug.1 → 3.4.4

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.
@@ -16,6 +16,7 @@ async function replaceOrDeleteItem(cosmosDb, container, config, id, partitionKey
16
16
  await cosmosDb.database(config.resourceNames.applicationStack).container(container).item(id, partitionKey).delete();
17
17
  }
18
18
  }
19
+ const DEFAULT_PAGE_SIZE = 100;
19
20
  async function search(cosmosDb, config, containerName, filters, limit, afterCursor, paginatedVersion = false, order, projections = '*') {
20
21
  const logger = (0, framework_common_helpers_1.getLogger)(config, 'query-helper#search');
21
22
  const filterExpression = buildFilterExpression(filters);
@@ -36,85 +37,50 @@ async function search(cosmosDb, config, containerName, filters, limit, afterCurs
36
37
  // - Legacy cursors: Numeric cursor.id values must use OFFSET/LIMIT for backward compatibility
37
38
  const canUseContinuationToken = !isDistinctQuery;
38
39
  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
40
  // Use Cosmos DB's continuation token pagination
48
41
  const feedOptions = {};
49
- if (limit) {
50
- feedOptions.maxItemCount = limit;
51
- }
52
42
  // Extract continuation token from the cursor (backward compatibility)
53
43
  if (afterCursor === null || afterCursor === void 0 ? void 0 : afterCursor.continuationToken) {
54
44
  feedOptions.continuationToken = afterCursor.continuationToken;
55
- logger.debug('🔍 PAGINATION DEBUG - Using provided continuation token:', {
56
- continuationToken: afterCursor.continuationToken,
57
- feedOptions,
58
- });
59
45
  }
60
- else if (!canUseContinuationToken || hasLegacyCursor) {
46
+ // Azure Cosmos DB requires maxItemCount when using continuation tokens
47
+ // Always set maxItemCount in the continuation token path to ensure consistent page sizes
48
+ if (limit || (afterCursor === null || afterCursor === void 0 ? void 0 : afterCursor.continuationToken) || canUseContinuationToken) {
49
+ feedOptions.maxItemCount = limit !== null && limit !== void 0 ? limit : DEFAULT_PAGE_SIZE;
50
+ }
51
+ if (!(afterCursor === null || afterCursor === void 0 ? void 0 : afterCursor.continuationToken) && (!canUseContinuationToken || hasLegacyCursor)) {
61
52
  // Legacy cursor format - fallback to OFFSET for backward compatibility
62
- const offset = (afterCursor === null || afterCursor === void 0 ? void 0 : afterCursor.id) ? parseInt(afterCursor.id) : 0;
63
- let legacyQuery = `${finalQuery} OFFSET ${offset}`;
64
- if (limit) {
65
- legacyQuery += ` LIMIT ${limit} `;
66
- }
53
+ const parsedLegacyId = (afterCursor === null || afterCursor === void 0 ? void 0 : afterCursor.id) ? parseInt(afterCursor.id, 10) : NaN;
54
+ const offset = Number.isFinite(parsedLegacyId) ? parsedLegacyId : 0;
55
+ // Azure Cosmos DB requires LIMIT when using OFFSET
56
+ const effectiveLimit = limit !== null && limit !== void 0 ? limit : DEFAULT_PAGE_SIZE;
57
+ const legacyQuery = `${finalQuery} OFFSET ${offset} LIMIT ${effectiveLimit} `;
67
58
  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
59
  const { resources } = await container.items.query(legacyQuerySpec).fetchAll();
75
60
  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
61
  return {
82
62
  items: processedResources !== null && processedResources !== void 0 ? processedResources : [],
83
63
  count: processedResources.length,
84
64
  cursor: {
85
- id: (offset + processedResources.length).toString(),
65
+ id: (offset + effectiveLimit).toString(),
86
66
  },
87
67
  };
88
68
  }
89
- logger.debug('🔍 PAGINATION DEBUG - About to execute continuation token query with feedOptions:', feedOptions);
90
69
  const queryIterator = container.items.query(querySpec, feedOptions);
91
70
  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
71
  const finalResources = processResources(resources || []);
72
+ // cursor.id advances by the page size (limit) to maintain consistent page-based offsets
73
+ // that frontends rely on (e.g., limit=5 produces cursors 5, 10 ,15, ...)
74
+ const parsedId = (afterCursor === null || afterCursor === void 0 ? void 0 : afterCursor.id) ? parseInt(afterCursor.id, 10) : NaN;
75
+ const previousOffset = Number.isFinite(parsedId) ? parsedId : 0;
76
+ const effectiveLimit = limit !== null && limit !== void 0 ? limit : DEFAULT_PAGE_SIZE;
100
77
  let cursor;
101
78
  if (continuationToken) {
102
- cursor = { continuationToken };
103
- logger.debug('🔍 PAGINATION DEBUG - Setting cursor with continuation token:', cursor);
79
+ cursor = { continuationToken, id: (previousOffset + effectiveLimit).toString() };
104
80
  }
105
81
  else if (finalResources.length > 0) {
106
- 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);
109
- }
110
- else {
111
- logger.debug('🔍 PAGINATION DEBUG - No continuation token and no resources, no cursor set');
82
+ cursor = { id: (previousOffset + effectiveLimit).toString() };
112
83
  }
113
- logger.debug('🔍 PAGINATION DEBUG - Final response:', {
114
- itemsCount: finalResources.length,
115
- cursor,
116
- hasMoreResults: !!continuationToken,
117
- });
118
84
  return {
119
85
  items: finalResources,
120
86
  count: finalResources.length,
@@ -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.1",
3
+ "version": "3.4.4",
4
4
  "description": "Handle Booster's integration with Azure",
5
5
  "keywords": [
6
6
  "framework-provider-azure"
@@ -27,14 +27,14 @@
27
27
  "@azure/functions": "^1.2.2",
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": "^3.4.4",
31
+ "@boostercloud/framework-types": "^3.4.4",
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": "^3.4.4",
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
+ }