@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
|
-
|
|
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
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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 +
|
|
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
|
-
|
|
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
|
|
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": "
|
|
31
|
-
"@boostercloud/framework-types": "
|
|
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": "
|
|
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
|
+
}
|