@fonoster/sipnet 0.16.8 → 0.17.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.
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2026 Fonoster Inc
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
18
18
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
20
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
21
|
+
SOFTWARE.
|
|
@@ -30,21 +30,29 @@ exports.listNumbers = listNumbers;
|
|
|
30
30
|
*/
|
|
31
31
|
const common_1 = require("@fonoster/common");
|
|
32
32
|
const logger_1 = require("@fonoster/logger");
|
|
33
|
+
const paginationUtils_1 = require("../resources/paginationUtils");
|
|
33
34
|
const convertToFonosterNumber_1 = require("./convertToFonosterNumber");
|
|
34
35
|
const logger = (0, logger_1.getLogger)({ service: "sipnet", filePath: __filename });
|
|
35
36
|
function listNumbers(api) {
|
|
36
37
|
const fn = (call, callback) => __awaiter(this, void 0, void 0, function* () {
|
|
37
38
|
const { request } = call;
|
|
38
39
|
logger.verbose("call to listNumbers", Object.assign({}, request));
|
|
39
|
-
const response = yield api.listNumbers(request);
|
|
40
40
|
const accessKeyId = (0, common_1.getAccessKeyIdFromCall)(call);
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
const requestWithPageToken = request;
|
|
42
|
+
const pageSize = requestWithPageToken.pageSize || 20;
|
|
43
|
+
const response = yield (0, paginationUtils_1.paginateWithFiltering)({
|
|
44
|
+
pageSize,
|
|
45
|
+
pageToken: requestWithPageToken.pageToken,
|
|
46
|
+
fetchPage: (pageToken, fetchPageSize) => __awaiter(this, void 0, void 0, function* () {
|
|
47
|
+
const normalizedRequest = Object.assign(Object.assign({}, request), { pageToken, pageSize: fetchPageSize });
|
|
48
|
+
return yield api.listNumbers(normalizedRequest);
|
|
49
|
+
}),
|
|
50
|
+
filterItems: (items) => {
|
|
51
|
+
// Filter by accessKeyId and convert to Fonoster number format
|
|
52
|
+
return (0, paginationUtils_1.filterByAccessKeyId)(items, accessKeyId).map(convertToFonosterNumber_1.convertToFonosterNumber);
|
|
53
|
+
}
|
|
47
54
|
});
|
|
55
|
+
callback(null, response);
|
|
48
56
|
});
|
|
49
57
|
return (0, common_1.withErrorHandlingAndValidation)(fn, common_1.Validators.listRequestSchema);
|
|
50
58
|
}
|
|
@@ -30,6 +30,7 @@ exports.listResources = listResources;
|
|
|
30
30
|
*/
|
|
31
31
|
const common_1 = require("@fonoster/common");
|
|
32
32
|
const logger_1 = require("@fonoster/logger");
|
|
33
|
+
const paginationUtils_1 = require("./paginationUtils");
|
|
33
34
|
const logger = (0, logger_1.getLogger)({ service: "sipnet", filePath: __filename });
|
|
34
35
|
function listResources(api, resource) {
|
|
35
36
|
const fn = (call, callback) => __awaiter(this, void 0, void 0, function* () {
|
|
@@ -37,12 +38,18 @@ function listResources(api, resource) {
|
|
|
37
38
|
const res = resource === "Credentials" ? "Credential" : resource;
|
|
38
39
|
logger.verbose(`call to list${res}s`, { request });
|
|
39
40
|
const accessKeyId = (0, common_1.getAccessKeyIdFromCall)(call);
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
const requestWithPageToken = request;
|
|
42
|
+
const pageSize = requestWithPageToken.pageSize || 20;
|
|
43
|
+
const response = yield (0, paginationUtils_1.paginateWithFiltering)({
|
|
44
|
+
pageSize,
|
|
45
|
+
pageToken: requestWithPageToken.pageToken,
|
|
46
|
+
fetchPage: (pageToken, fetchPageSize) => __awaiter(this, void 0, void 0, function* () {
|
|
47
|
+
const normalizedRequest = Object.assign(Object.assign({}, request), { pageToken, pageSize: fetchPageSize });
|
|
48
|
+
return yield api[`list${res}s`](normalizedRequest);
|
|
49
|
+
}),
|
|
50
|
+
filterItems: (items) => (0, paginationUtils_1.filterByAccessKeyId)(items, accessKeyId)
|
|
45
51
|
});
|
|
52
|
+
callback(null, response);
|
|
46
53
|
});
|
|
47
54
|
return (0, common_1.withErrorHandlingAndValidation)(fn, common_1.Validators.listRequestSchema);
|
|
48
55
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) 2025 by Fonoster Inc (https://fonoster.com)
|
|
3
|
+
* http://github.com/fonoster/fonoster
|
|
4
|
+
*
|
|
5
|
+
* This file is part of Fonoster
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the MIT License (the "License");
|
|
8
|
+
* you may not use this file except in compliance with
|
|
9
|
+
* the License. You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* https://opensource.org/licenses/MIT
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
* See the License for the specific language governing permissions and
|
|
17
|
+
* limitations under the License.
|
|
18
|
+
*/
|
|
19
|
+
type ListResponse<T> = {
|
|
20
|
+
items: T[];
|
|
21
|
+
nextPageToken?: string;
|
|
22
|
+
};
|
|
23
|
+
type PaginatedListOptions<TInput, TOutput = TInput> = {
|
|
24
|
+
pageSize: number;
|
|
25
|
+
pageToken?: string;
|
|
26
|
+
fetchPage: (pageToken: string | undefined, pageSize: number) => Promise<ListResponse<TInput>>;
|
|
27
|
+
filterItems: (items: TInput[]) => TOutput[];
|
|
28
|
+
maxIterations?: number;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Normalizes empty string pageToken to undefined.
|
|
32
|
+
* Empty strings are falsy in JS, but gRPC/protobuf may send them as empty strings.
|
|
33
|
+
*/
|
|
34
|
+
export declare function normalizePageToken(pageToken?: string): string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Normalizes empty string nextPageToken from response to undefined.
|
|
37
|
+
*/
|
|
38
|
+
export declare function normalizeNextPageToken(nextPageToken?: string): string | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Filters items by accessKeyId from extended field.
|
|
41
|
+
*/
|
|
42
|
+
export declare function filterByAccessKeyId<T extends {
|
|
43
|
+
extended?: {
|
|
44
|
+
accessKeyId?: string;
|
|
45
|
+
};
|
|
46
|
+
}>(items: T[], accessKeyId: string): T[];
|
|
47
|
+
/**
|
|
48
|
+
* Handles pagination with filtering, continuing to fetch pages until:
|
|
49
|
+
* - We have enough items (pageSize)
|
|
50
|
+
* - We run out of pages
|
|
51
|
+
* - We hit the max iterations limit
|
|
52
|
+
*
|
|
53
|
+
* This handles the case where the first pages contain items from other customers.
|
|
54
|
+
*
|
|
55
|
+
* @template TInput - The type of items returned from the API
|
|
56
|
+
* @template TOutput - The type of items after filtering/transformation (defaults to TInput)
|
|
57
|
+
*/
|
|
58
|
+
export declare function paginateWithFiltering<TInput, TOutput = TInput>(options: PaginatedListOptions<TInput, TOutput>): Promise<ListResponse<TOutput>>;
|
|
59
|
+
export {};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.normalizePageToken = normalizePageToken;
|
|
13
|
+
exports.normalizeNextPageToken = normalizeNextPageToken;
|
|
14
|
+
exports.filterByAccessKeyId = filterByAccessKeyId;
|
|
15
|
+
exports.paginateWithFiltering = paginateWithFiltering;
|
|
16
|
+
/**
|
|
17
|
+
* Normalizes empty string pageToken to undefined.
|
|
18
|
+
* Empty strings are falsy in JS, but gRPC/protobuf may send them as empty strings.
|
|
19
|
+
*/
|
|
20
|
+
function normalizePageToken(pageToken) {
|
|
21
|
+
return pageToken === "" ? undefined : pageToken;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Normalizes empty string nextPageToken from response to undefined.
|
|
25
|
+
*/
|
|
26
|
+
function normalizeNextPageToken(nextPageToken) {
|
|
27
|
+
return nextPageToken && nextPageToken !== "" ? nextPageToken : undefined;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Filters items by accessKeyId from extended field.
|
|
31
|
+
*/
|
|
32
|
+
function filterByAccessKeyId(items, accessKeyId) {
|
|
33
|
+
return items.filter((item) => { var _a; return ((_a = item.extended) === null || _a === void 0 ? void 0 : _a.accessKeyId) === accessKeyId; });
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Handles pagination with filtering, continuing to fetch pages until:
|
|
37
|
+
* - We have enough items (pageSize)
|
|
38
|
+
* - We run out of pages
|
|
39
|
+
* - We hit the max iterations limit
|
|
40
|
+
*
|
|
41
|
+
* This handles the case where the first pages contain items from other customers.
|
|
42
|
+
*
|
|
43
|
+
* @template TInput - The type of items returned from the API
|
|
44
|
+
* @template TOutput - The type of items after filtering/transformation (defaults to TInput)
|
|
45
|
+
*/
|
|
46
|
+
function paginateWithFiltering(options) {
|
|
47
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
48
|
+
const { pageSize, pageToken, fetchPage, filterItems, maxIterations = 10 } = options;
|
|
49
|
+
const normalizedPageToken = normalizePageToken(pageToken);
|
|
50
|
+
const items = [];
|
|
51
|
+
let nextPageToken;
|
|
52
|
+
let currentPageToken = normalizedPageToken;
|
|
53
|
+
let hasMorePages = true;
|
|
54
|
+
let iterations = 0;
|
|
55
|
+
while (items.length < pageSize &&
|
|
56
|
+
hasMorePages &&
|
|
57
|
+
iterations < maxIterations) {
|
|
58
|
+
iterations++;
|
|
59
|
+
// Request more items to account for filtering
|
|
60
|
+
const backendRequestedSize = pageSize * 2;
|
|
61
|
+
const response = yield fetchPage(currentPageToken, backendRequestedSize);
|
|
62
|
+
if (!response || !response.items) {
|
|
63
|
+
hasMorePages = false;
|
|
64
|
+
nextPageToken = undefined;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
const filteredItems = filterItems(response.items);
|
|
68
|
+
items.push(...filteredItems);
|
|
69
|
+
const backendNextPageToken = normalizeNextPageToken(response.nextPageToken);
|
|
70
|
+
// If we got no items from backend or no nextPageToken, we're done
|
|
71
|
+
if (response.items.length === 0 || !backendNextPageToken) {
|
|
72
|
+
hasMorePages = false;
|
|
73
|
+
nextPageToken = undefined;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
// Check if backend returned fewer items than requested (indicating last page)
|
|
77
|
+
// If backend returned exactly what we asked for or more, there might be more items
|
|
78
|
+
const backendHasMoreItems = response.items.length >= backendRequestedSize;
|
|
79
|
+
// If we've filled the page, we're done
|
|
80
|
+
if (items.length >= pageSize) {
|
|
81
|
+
// We filled the page, so there might be more items
|
|
82
|
+
// Only return nextPageToken if backend indicates there are more items
|
|
83
|
+
nextPageToken = backendHasMoreItems ? backendNextPageToken : undefined;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
// If we got filtered items in this iteration, return them
|
|
87
|
+
// Only continue if we got 0 filtered items (meaning all items were from other customers)
|
|
88
|
+
if (filteredItems.length === 0) {
|
|
89
|
+
// No items matched in this page, continue to next page
|
|
90
|
+
currentPageToken = backendNextPageToken;
|
|
91
|
+
nextPageToken = backendNextPageToken;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
nextPageToken = backendHasMoreItems ? backendNextPageToken : undefined;
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
items: items.slice(0, pageSize),
|
|
100
|
+
nextPageToken
|
|
101
|
+
};
|
|
102
|
+
});
|
|
103
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fonoster/sipnet",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Routr-based SIP stack",
|
|
5
5
|
"author": "Pedro Sanders <psanders@fonoster.com>",
|
|
6
6
|
"homepage": "https://github.com/fonoster/fonoster#readme",
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
"fonoster": "./dist/index.js"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@fonoster/common": "^0.
|
|
24
|
-
"@fonoster/identity": "^0.
|
|
25
|
-
"@fonoster/logger": "^0.
|
|
26
|
-
"@fonoster/types": "^0.
|
|
23
|
+
"@fonoster/common": "^0.17.0",
|
|
24
|
+
"@fonoster/identity": "^0.17.0",
|
|
25
|
+
"@fonoster/logger": "^0.17.0",
|
|
26
|
+
"@fonoster/types": "^0.17.0",
|
|
27
27
|
"@grpc/grpc-js": "~1.10.6",
|
|
28
28
|
"@routr/sdk": "2.13.1",
|
|
29
29
|
"zod": "^3.23.8"
|
|
@@ -41,5 +41,5 @@
|
|
|
41
41
|
"bugs": {
|
|
42
42
|
"url": "https://github.com/fonoster/fonoster/issues"
|
|
43
43
|
},
|
|
44
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "4d1a9afaec6f294184386e009d1a4e292fb3583b"
|
|
45
45
|
}
|