@boostercloud/framework-provider-azure 3.4.1 → 3.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/helpers/query-helper.js +74 -27
- package/package.json +4 -4
|
@@ -21,45 +21,92 @@ async function search(cosmosDb, config, containerName, filters, limit, afterCurs
|
|
|
21
21
|
const filterExpression = buildFilterExpression(filters);
|
|
22
22
|
const projectionsExpression = buildProjections(projections);
|
|
23
23
|
const queryDefinition = `SELECT ${projectionsExpression} FROM c ${filterExpression !== '' ? `WHERE ${filterExpression}` : filterExpression}`;
|
|
24
|
-
const
|
|
25
|
-
let finalQuery = queryWithOrder;
|
|
26
|
-
if (paginatedVersion && limit) {
|
|
27
|
-
finalQuery += ` OFFSET ${(afterCursor === null || afterCursor === void 0 ? void 0 : afterCursor.id) || 0} LIMIT ${limit} `;
|
|
28
|
-
}
|
|
29
|
-
else {
|
|
30
|
-
if (limit) {
|
|
31
|
-
finalQuery += ` OFFSET 0 LIMIT ${limit} `;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
24
|
+
const finalQuery = queryDefinition + buildOrderExpression(order);
|
|
34
25
|
const querySpec = {
|
|
35
26
|
query: finalQuery,
|
|
36
27
|
parameters: buildExpressionAttributeValues(filters),
|
|
37
28
|
};
|
|
38
29
|
logger.debug('Running search with the following params: \n', JSON.stringify(querySpec));
|
|
39
|
-
|
|
40
|
-
.database(config.resourceNames.applicationStack)
|
|
41
|
-
.container(containerName)
|
|
42
|
-
.items.query(querySpec)
|
|
43
|
-
.fetchAll();
|
|
44
|
-
resources = nestProperties(resources);
|
|
45
|
-
resources = resources.map((resource) => ({
|
|
46
|
-
...resource,
|
|
47
|
-
boosterMetadata: {
|
|
48
|
-
...resource.boosterMetadata,
|
|
49
|
-
optimisticConcurrencyValue: resource._etag,
|
|
50
|
-
},
|
|
51
|
-
}));
|
|
30
|
+
const container = cosmosDb.database(config.resourceNames.applicationStack).container(containerName);
|
|
52
31
|
if (paginatedVersion) {
|
|
32
|
+
const isDistinctQuery = /SELECT\s+DISTINCT\s+/i.test(finalQuery);
|
|
33
|
+
// Azure Cosmos DB continuation token compatibility rules:
|
|
34
|
+
// - Regular queries: Always use continuation tokens
|
|
35
|
+
// - DISTINCT queries: Always use OFFSET/LIMIT fallback (continuation tokens unreliable even with ORDER BY)
|
|
36
|
+
// - Legacy cursors: Numeric cursor.id values must use OFFSET/LIMIT for backward compatibility
|
|
37
|
+
const canUseContinuationToken = !isDistinctQuery;
|
|
38
|
+
const hasLegacyCursor = typeof (afterCursor === null || afterCursor === void 0 ? void 0 : afterCursor.id) === 'string' && /^\d+$/.test(afterCursor.id);
|
|
39
|
+
// Use Cosmos DB's continuation token pagination
|
|
40
|
+
const feedOptions = {};
|
|
41
|
+
if (limit) {
|
|
42
|
+
feedOptions.maxItemCount = limit;
|
|
43
|
+
}
|
|
44
|
+
// Extract continuation token from the cursor (backward compatibility)
|
|
45
|
+
if (afterCursor === null || afterCursor === void 0 ? void 0 : afterCursor.continuationToken) {
|
|
46
|
+
feedOptions.continuationToken = afterCursor.continuationToken;
|
|
47
|
+
}
|
|
48
|
+
else if (!canUseContinuationToken || hasLegacyCursor) {
|
|
49
|
+
// Legacy cursor format - fallback to OFFSET for backward compatibility
|
|
50
|
+
const offset = (afterCursor === null || afterCursor === void 0 ? void 0 : afterCursor.id) ? parseInt(afterCursor.id) : 0;
|
|
51
|
+
let legacyQuery = `${finalQuery} OFFSET ${offset}`;
|
|
52
|
+
if (limit) {
|
|
53
|
+
legacyQuery += ` LIMIT ${limit} `;
|
|
54
|
+
}
|
|
55
|
+
const legacyQuerySpec = { ...querySpec, query: legacyQuery };
|
|
56
|
+
const { resources } = await container.items.query(legacyQuerySpec).fetchAll();
|
|
57
|
+
const processedResources = processResources(resources);
|
|
58
|
+
return {
|
|
59
|
+
items: processedResources !== null && processedResources !== void 0 ? processedResources : [],
|
|
60
|
+
count: processedResources.length,
|
|
61
|
+
cursor: {
|
|
62
|
+
id: (offset + processedResources.length).toString(),
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const queryIterator = container.items.query(querySpec, feedOptions);
|
|
67
|
+
const { resources, continuationToken } = await queryIterator.fetchNext();
|
|
68
|
+
const finalResources = processResources(resources || []);
|
|
69
|
+
let cursor;
|
|
70
|
+
if (continuationToken) {
|
|
71
|
+
cursor = { continuationToken };
|
|
72
|
+
}
|
|
73
|
+
else if (finalResources.length > 0) {
|
|
74
|
+
const currentOffset = (afterCursor === null || afterCursor === void 0 ? void 0 : afterCursor.id) && !isNaN(parseInt(afterCursor.id)) ? parseInt(afterCursor.id) : 0;
|
|
75
|
+
cursor = { id: (currentOffset + finalResources.length).toString() }; // Use the length of the results to calculate the next id
|
|
76
|
+
}
|
|
53
77
|
return {
|
|
54
|
-
items:
|
|
55
|
-
count:
|
|
56
|
-
cursor
|
|
78
|
+
items: finalResources,
|
|
79
|
+
count: finalResources.length,
|
|
80
|
+
cursor,
|
|
57
81
|
};
|
|
58
82
|
}
|
|
59
83
|
else {
|
|
60
|
-
|
|
84
|
+
// Non-paginated version - apply limit but fetch all results
|
|
85
|
+
let finalQueryForNonPaginated = finalQuery;
|
|
86
|
+
if (limit) {
|
|
87
|
+
finalQueryForNonPaginated += ` OFFSET 0 LIMIT ${limit} `;
|
|
88
|
+
}
|
|
89
|
+
const nonPaginatedQuerySpec = { ...querySpec, query: finalQueryForNonPaginated };
|
|
90
|
+
const { resources } = await container.items.query(nonPaginatedQuerySpec).fetchAll();
|
|
91
|
+
const processedResources = processResources(resources);
|
|
92
|
+
return processedResources !== null && processedResources !== void 0 ? processedResources : [];
|
|
61
93
|
}
|
|
62
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Processes raw Cosmos DB resources by nesting properties and adding Booster metadata
|
|
97
|
+
* @param resources - The raw resources to process
|
|
98
|
+
* @returns An array of processed resources with nested properties and Booster metadata
|
|
99
|
+
*/
|
|
100
|
+
function processResources(resources) {
|
|
101
|
+
const nestedResources = nestProperties(resources);
|
|
102
|
+
return nestedResources.map((resource) => ({
|
|
103
|
+
...resource,
|
|
104
|
+
boosterMetadata: {
|
|
105
|
+
...resource.boosterMetadata,
|
|
106
|
+
optimisticConcurrencyValue: resource._etag,
|
|
107
|
+
},
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
63
110
|
function buildFilterExpression(filters, usedPlaceholders = []) {
|
|
64
111
|
return Object.entries(filters)
|
|
65
112
|
.map(([propName, filter]) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@boostercloud/framework-provider-azure",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.2",
|
|
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": "^3.4.
|
|
31
|
-
"@boostercloud/framework-types": "^3.4.
|
|
30
|
+
"@boostercloud/framework-common-helpers": "^3.4.2",
|
|
31
|
+
"@boostercloud/framework-types": "^3.4.2",
|
|
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": "^3.4.
|
|
37
|
+
"@boostercloud/eslint-config": "^3.4.2",
|
|
38
38
|
"@types/chai": "4.2.18",
|
|
39
39
|
"@types/chai-as-promised": "7.1.4",
|
|
40
40
|
"@types/faker": "5.1.5",
|