├── .github ├── publish.yml ├── auto-approve.yml ├── auto-label.yaml ├── release-please.yml ├── release-trigger.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── processs_request.md │ ├── questions.md │ ├── feature_request.yml │ ├── documentation_request.yml │ └── bug_report.yml ├── CODEOWNERS ├── workflows │ ├── issues-no-repro.yaml │ ├── response.yaml │ └── ci.yaml ├── sync-repo-settings.yaml ├── .OwlBot.yaml ├── .OwlBot.lock.yaml ├── generated-files-bot.yml ├── PULL_REQUEST_TEMPLATE.md └── scripts │ ├── remove-response-label.cjs │ ├── close-invalid-link.cjs │ ├── close-unresponsive.cjs │ ├── fixtures │ ├── invalidIssueBody.txt │ ├── validIssueBody.txt │ └── validIssueBodyDifferentLinkLocation.txt │ └── tests │ ├── close-invalid-link.test.cjs │ └── close-or-remove-response-label.test.cjs ├── .kokoro ├── presubmit │ ├── node18 │ │ ├── test.cfg │ │ ├── system-test.cfg │ │ ├── samples-test.cfg │ │ └── common.cfg │ └── windows │ │ ├── common.cfg │ │ └── test.cfg ├── continuous │ └── node18 │ │ ├── test.cfg │ │ ├── lint.cfg │ │ ├── system-test.cfg │ │ ├── samples-test.cfg │ │ └── common.cfg ├── .gitattributes ├── release │ ├── common.cfg │ ├── docs.cfg │ ├── docs-devsite.cfg │ ├── docs-devsite.sh │ ├── publish.cfg │ └── docs.sh ├── common.cfg ├── docs.sh ├── lint.sh ├── test.bat ├── trampoline.sh ├── publish.sh ├── test.sh ├── system-test.sh ├── samples-test.sh ├── populate-secrets.sh └── trampoline_v2.sh ├── samples ├── .eslintrc.yml ├── package.json ├── test │ └── test.js ├── quickstart.js ├── README.md └── streamify.js ├── .eslintrc.json ├── .prettierignore ├── .gitignore ├── .eslintignore ├── .gitattributes ├── linkinator.config.json ├── .compodocrc ├── tsconfig.json ├── SECURITY.md ├── .repo-metadata.json ├── renovate.json ├── .nycrc ├── system-test └── system.ts ├── .prettierrc.js ├── owlbot.py ├── .mocharc.js ├── .jsdoc.js ├── .trampolinerc ├── package.json ├── CONTRIBUTING.md ├── src ├── resource-stream.ts └── index.ts ├── CODE_OF_CONDUCT.md ├── README.md ├── test ├── resource-stream.ts └── index.ts ├── LICENSE └── CHANGELOG.md /.github/publish.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.kokoro/presubmit/node18/test.cfg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.kokoro/continuous/node18/test.cfg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.kokoro/.gitattributes: -------------------------------------------------------------------------------- 1 | * linguist-generated=true 2 | -------------------------------------------------------------------------------- /.github/auto-approve.yml: -------------------------------------------------------------------------------- 1 | processes: 2 | - "NodeDependency" -------------------------------------------------------------------------------- /.github/auto-label.yaml: -------------------------------------------------------------------------------- 1 | requestsize: 2 | enabled: true 3 | -------------------------------------------------------------------------------- /samples/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | --- 2 | rules: 3 | no-console: off 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/gts" 3 | } 4 | -------------------------------------------------------------------------------- /.github/release-please.yml: -------------------------------------------------------------------------------- 1 | handleGHRelease: true 2 | releaseType: node 3 | -------------------------------------------------------------------------------- /.github/release-trigger.yml: -------------------------------------------------------------------------------- 1 | enabled: true 2 | multiScmName: nodejs-paginator -------------------------------------------------------------------------------- /.kokoro/presubmit/windows/common.cfg: -------------------------------------------------------------------------------- 1 | # Format: //devtools/kokoro/config/proto/build.proto 2 | 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/coverage 3 | test/fixtures 4 | build/ 5 | docs/ 6 | protos/ 7 | -------------------------------------------------------------------------------- /.kokoro/presubmit/windows/test.cfg: -------------------------------------------------------------------------------- 1 | # Use the test file directly 2 | build_file: "nodejs-paginator/.kokoro/test.bat" 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .nyc_output 3 | build 4 | package-lock.json 5 | docs/ 6 | .coverage 7 | __pycache__ 8 | .DS_Store -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/coverage 3 | test/fixtures 4 | build/ 5 | docs/ 6 | protos/ 7 | samples/generated/ 8 | system-test/**/fixtures 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.ts text eol=lf 2 | *.js text eol=lf 3 | protos/* linguist-generated 4 | **/api-extractor.json linguist-language=JSON-with-Comments 5 | -------------------------------------------------------------------------------- /.kokoro/continuous/node18/lint.cfg: -------------------------------------------------------------------------------- 1 | env_vars: { 2 | key: "TRAMPOLINE_BUILD_FILE" 3 | value: "github/nodejs-paginator/.kokoro/lint.sh" 4 | } 5 | -------------------------------------------------------------------------------- /.kokoro/release/common.cfg: -------------------------------------------------------------------------------- 1 | before_action { 2 | fetch_keystore { 3 | keystore_resource { 4 | keystore_config_id: 73713 5 | keyname: "yoshi-automation-github-key" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: Google Cloud Support 3 | url: https://cloud.google.com/support/ 4 | about: If you have a support contract with Google, please use the Google Cloud Support portal. 5 | -------------------------------------------------------------------------------- /linkinator.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "recurse": true, 3 | "skip": [ 4 | "https://codecov.io/gh/googleapis/", 5 | "www.googleapis.com", 6 | "img.shields.io" 7 | ], 8 | "silent": true, 9 | "concurrency": 10 10 | } 11 | -------------------------------------------------------------------------------- /.compodocrc: -------------------------------------------------------------------------------- 1 | --- 2 | tsconfig: ./tsconfig.json 3 | output: ./docs 4 | theme: material 5 | hideGenerator: true 6 | disablePrivate: true 7 | disableProtected: true 8 | disableInternal: true 9 | disableCoverage: true 10 | disableGraph: true 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/processs_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Process Request 3 | about: Submit a process request to the library. Process requests are any requests related to library infrastructure, for example CI/CD, publishing, releasing, broken links. 4 | --- 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/gts/tsconfig-google.json", 3 | "compilerOptions": { 4 | "lib": ["es2018", "dom"], 5 | "rootDir": ".", 6 | "outDir": "build" 7 | }, 8 | "include": [ 9 | "src/*.ts", 10 | "test/*.ts", 11 | "system-test/*.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/questions.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: If you have a question, please use Discussions 4 | 5 | --- 6 | 7 | If you have a general question that goes beyond the library itself, we encourage you to use [Discussions](https://github.com//discussions) 8 | to engage with fellow community members! 9 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | To report a security issue, please use [g.co/vulnz](https://g.co/vulnz). 4 | 5 | The Google Security Team will respond within 5 working days of your report on g.co/vulnz. 6 | 7 | We use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue. 8 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Code owners file. 2 | # This file controls who is tagged for review for any given pull request. 3 | # 4 | # For syntax help see: 5 | # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners#codeowners-syntax 6 | 7 | 8 | # Unless specified, the jsteam is the default owner for nodejs repositories. 9 | * jsteam-handwritten-libraries @googleapis/jsteam -------------------------------------------------------------------------------- /.kokoro/continuous/node18/system-test.cfg: -------------------------------------------------------------------------------- 1 | # Download resources for system tests (service account key, etc.) 2 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs" 3 | 4 | env_vars: { 5 | key: "TRAMPOLINE_BUILD_FILE" 6 | value: "github/nodejs-paginator/.kokoro/system-test.sh" 7 | } 8 | 9 | env_vars: { 10 | key: "SECRET_MANAGER_KEYS" 11 | value: "long-door-651-kokoro-system-test-service-account" 12 | } -------------------------------------------------------------------------------- /.kokoro/presubmit/node18/system-test.cfg: -------------------------------------------------------------------------------- 1 | # Download resources for system tests (service account key, etc.) 2 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs" 3 | 4 | env_vars: { 5 | key: "TRAMPOLINE_BUILD_FILE" 6 | value: "github/nodejs-paginator/.kokoro/system-test.sh" 7 | } 8 | 9 | env_vars: { 10 | key: "SECRET_MANAGER_KEYS" 11 | value: "long-door-651-kokoro-system-test-service-account" 12 | } -------------------------------------------------------------------------------- /.kokoro/continuous/node18/samples-test.cfg: -------------------------------------------------------------------------------- 1 | # Download resources for system tests (service account key, etc.) 2 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs" 3 | 4 | env_vars: { 5 | key: "TRAMPOLINE_BUILD_FILE" 6 | value: "github/nodejs-paginator/.kokoro/samples-test.sh" 7 | } 8 | 9 | env_vars: { 10 | key: "SECRET_MANAGER_KEYS" 11 | value: "long-door-651-kokoro-system-test-service-account" 12 | } -------------------------------------------------------------------------------- /.kokoro/presubmit/node18/samples-test.cfg: -------------------------------------------------------------------------------- 1 | # Download resources for system tests (service account key, etc.) 2 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/google-cloud-nodejs" 3 | 4 | env_vars: { 5 | key: "TRAMPOLINE_BUILD_FILE" 6 | value: "github/nodejs-paginator/.kokoro/samples-test.sh" 7 | } 8 | 9 | env_vars: { 10 | key: "SECRET_MANAGER_KEYS" 11 | value: "long-door-651-kokoro-system-test-service-account" 12 | } -------------------------------------------------------------------------------- /.repo-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "paginator", 3 | "name_pretty": "Google Cloud Common Paginator", 4 | "release_level": "stable", 5 | "language": "nodejs", 6 | "repo": "googleapis/nodejs-paginator", 7 | "distribution_name": "@google-cloud/paginator", 8 | "client_documentation": "https://cloud.google.com/nodejs/docs/reference/paginator/latest", 9 | "library_type": "OTHER", 10 | "codeowner_team": "jsteam-handwritten-libraries" 11 | } 12 | -------------------------------------------------------------------------------- /samples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Samples for the nodejs-paginator npm module.", 3 | "license": "Apache-2.0", 4 | "author": "Google LLC", 5 | "engines": { 6 | "node": ">=18" 7 | }, 8 | "repository": "googleapis/nodejs-paginator", 9 | "private": true, 10 | "scripts": { 11 | "test": "mocha" 12 | }, 13 | "dependencies": { 14 | "@google-cloud/paginator": "^6.0.0" 15 | }, 16 | "devDependencies": { 17 | "mocha": "^8.0.0" 18 | } 19 | } -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | "docker:disable", 5 | ":disableDependencyDashboard" 6 | ], 7 | "constraintsFiltering": "strict", 8 | "pinVersions": false, 9 | "rebaseStalePrs": true, 10 | "schedule": [ 11 | "after 9am and before 3pm" 12 | ], 13 | "gitAuthor": null, 14 | "packageRules": [ 15 | { 16 | "extends": "packages:linters", 17 | "groupName": "linters" 18 | } 19 | ], 20 | "ignoreDeps": ["typescript"] 21 | } 22 | -------------------------------------------------------------------------------- /.github/workflows/issues-no-repro.yaml: -------------------------------------------------------------------------------- 1 | name: invalid_link 2 | on: 3 | issues: 4 | types: [opened, reopened] 5 | 6 | jobs: 7 | close: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | pull-requests: write 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/github-script@v7 15 | with: 16 | script: | 17 | const script = require('./.github/scripts/close-invalid-link.cjs') 18 | await script({github, context}) 19 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "report-dir": "./.coverage", 3 | "reporter": ["text", "lcov"], 4 | "exclude": [ 5 | "**/*-test", 6 | "**/.coverage", 7 | "**/apis", 8 | "**/benchmark", 9 | "**/conformance", 10 | "**/docs", 11 | "**/samples", 12 | "**/scripts", 13 | "**/protos", 14 | "**/test", 15 | "**/*.d.ts", 16 | ".jsdoc.js", 17 | "**/.jsdoc.js", 18 | "karma.conf.js", 19 | "webpack-tests.config.js", 20 | "webpack.config.js" 21 | ], 22 | "exclude-after-remap": false, 23 | "all": true 24 | } 25 | -------------------------------------------------------------------------------- /.github/sync-repo-settings.yaml: -------------------------------------------------------------------------------- 1 | branchProtectionRules: 2 | - pattern: main 3 | isAdminEnforced: true 4 | requiredApprovingReviewCount: 1 5 | requiresCodeOwnerReviews: true 6 | requiresStrictStatusChecks: true 7 | requiredStatusCheckContexts: 8 | - "ci/kokoro: Samples test" 9 | - "ci/kokoro: System test" 10 | - lint 11 | - test (18) 12 | - test (20) 13 | - test (22) 14 | - cla/google 15 | - windows 16 | - OwlBot Post Processor 17 | permissionRules: 18 | - team: yoshi-admins 19 | permission: admin 20 | - team: jsteam-admins 21 | permission: admin 22 | - team: jsteam 23 | permission: push 24 | -------------------------------------------------------------------------------- /samples/test/test.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | console.warn('no sample tests available 👻'); 16 | -------------------------------------------------------------------------------- /system-test/system.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | console.warn('no system tests available 👻'); 16 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | module.exports = { 16 | ...require('gts/.prettierrc.json') 17 | } 18 | -------------------------------------------------------------------------------- /.kokoro/common.cfg: -------------------------------------------------------------------------------- 1 | # Format: //devtools/kokoro/config/proto/build.proto 2 | 3 | # Build logs will be here 4 | action { 5 | define_artifacts { 6 | regex: "**/*sponge_log.xml" 7 | } 8 | } 9 | 10 | # Download trampoline resources. 11 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" 12 | 13 | # Use the trampoline script to run in docker. 14 | build_file: "nodejs-paginator/.kokoro/trampoline_v2.sh" 15 | 16 | # Configure the docker image for kokoro-trampoline. 17 | env_vars: { 18 | key: "TRAMPOLINE_IMAGE" 19 | value: "gcr.io/cloud-devrel-kokoro-resources/node:14-user" 20 | } 21 | env_vars: { 22 | key: "TRAMPOLINE_BUILD_FILE" 23 | value: "github/nodejs-paginator/.kokoro/test.sh" 24 | } 25 | -------------------------------------------------------------------------------- /.kokoro/continuous/node18/common.cfg: -------------------------------------------------------------------------------- 1 | # Format: //devtools/kokoro/config/proto/build.proto 2 | 3 | # Build logs will be here 4 | action { 5 | define_artifacts { 6 | regex: "**/*sponge_log.xml" 7 | } 8 | } 9 | 10 | # Download trampoline resources. 11 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" 12 | 13 | # Use the trampoline script to run in docker. 14 | build_file: "nodejs-paginator/.kokoro/trampoline_v2.sh" 15 | 16 | # Configure the docker image for kokoro-trampoline. 17 | env_vars: { 18 | key: "TRAMPOLINE_IMAGE" 19 | value: "gcr.io/cloud-devrel-kokoro-resources/node:18-user" 20 | } 21 | env_vars: { 22 | key: "TRAMPOLINE_BUILD_FILE" 23 | value: "github/nodejs-paginator/.kokoro/test.sh" 24 | } 25 | -------------------------------------------------------------------------------- /.kokoro/presubmit/node18/common.cfg: -------------------------------------------------------------------------------- 1 | # Format: //devtools/kokoro/config/proto/build.proto 2 | 3 | # Build logs will be here 4 | action { 5 | define_artifacts { 6 | regex: "**/*sponge_log.xml" 7 | } 8 | } 9 | 10 | # Download trampoline resources. 11 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" 12 | 13 | # Use the trampoline script to run in docker. 14 | build_file: "nodejs-paginator/.kokoro/trampoline_v2.sh" 15 | 16 | # Configure the docker image for kokoro-trampoline. 17 | env_vars: { 18 | key: "TRAMPOLINE_IMAGE" 19 | value: "gcr.io/cloud-devrel-kokoro-resources/node:18-user" 20 | } 21 | env_vars: { 22 | key: "TRAMPOLINE_BUILD_FILE" 23 | value: "github/nodejs-paginator/.kokoro/test.sh" 24 | } 25 | -------------------------------------------------------------------------------- /.kokoro/release/docs.cfg: -------------------------------------------------------------------------------- 1 | # service account used to publish up-to-date docs. 2 | before_action { 3 | fetch_keystore { 4 | keystore_resource { 5 | keystore_config_id: 73713 6 | keyname: "docuploader_service_account" 7 | } 8 | } 9 | } 10 | 11 | # doc publications use a Python image. 12 | env_vars: { 13 | key: "TRAMPOLINE_IMAGE" 14 | value: "gcr.io/cloud-devrel-kokoro-resources/node:18-user" 15 | } 16 | 17 | # Download trampoline resources. 18 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" 19 | 20 | # Use the trampoline script to run in docker. 21 | build_file: "nodejs-paginator/.kokoro/trampoline_v2.sh" 22 | 23 | env_vars: { 24 | key: "TRAMPOLINE_BUILD_FILE" 25 | value: "github/nodejs-paginator/.kokoro/release/docs.sh" 26 | } 27 | -------------------------------------------------------------------------------- /.kokoro/release/docs-devsite.cfg: -------------------------------------------------------------------------------- 1 | # service account used to publish up-to-date docs. 2 | before_action { 3 | fetch_keystore { 4 | keystore_resource { 5 | keystore_config_id: 73713 6 | keyname: "docuploader_service_account" 7 | } 8 | } 9 | } 10 | 11 | # doc publications use a Python image. 12 | env_vars: { 13 | key: "TRAMPOLINE_IMAGE" 14 | value: "gcr.io/cloud-devrel-kokoro-resources/node:18-user" 15 | } 16 | 17 | # Download trampoline resources. 18 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" 19 | 20 | # Use the trampoline script to run in docker. 21 | build_file: "nodejs-paginator/.kokoro/trampoline_v2.sh" 22 | 23 | env_vars: { 24 | key: "TRAMPOLINE_BUILD_FILE" 25 | value: "github/nodejs-paginator/.kokoro/release/docs-devsite.sh" 26 | } 27 | -------------------------------------------------------------------------------- /.github/.OwlBot.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | docker: 15 | image: gcr.io/cloud-devrel-public-resources/owlbot-nodejs:latest 16 | 17 | 18 | begin-after-commit-hash: 397c0bfd367a2427104f988d5329bc117caafd95 19 | 20 | -------------------------------------------------------------------------------- /samples/quickstart.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // [START paginator_quickstart] 16 | const {paginator} = require('@google-cloud/paginator'); 17 | console.log(paginator); 18 | // [END paginator_quickstart] 19 | -------------------------------------------------------------------------------- /.kokoro/docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -eo pipefail 18 | 19 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global 20 | 21 | cd $(dirname $0)/.. 22 | 23 | npm install 24 | 25 | npm run docs-test 26 | -------------------------------------------------------------------------------- /.github/.OwlBot.lock.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | docker: 15 | image: gcr.io/cloud-devrel-public-resources/owlbot-nodejs:latest 16 | digest: sha256:c7e4968cfc97a204a4b2381f3ecb55cabc40c4cccf88b1ef8bef0d976be87fee 17 | # created: 2025-04-08T17:33:08.498793944Z 18 | -------------------------------------------------------------------------------- /owlbot.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Google LLC. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | import synthtool as s 15 | import synthtool.gcp as gcp 16 | 17 | common_templates = gcp.CommonTemplates() 18 | templates = common_templates.node_library() 19 | s.copy(sources=templates, excludes=["LICENSE", "README.md", ".github/ISSUE_TEMPLATE", ".github/scripts", ".kokoro", ".github/workflows/issues-no-repro.yaml", ".jsdoc.js"]) 20 | -------------------------------------------------------------------------------- /.kokoro/lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -eo pipefail 18 | 19 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global 20 | 21 | cd $(dirname $0)/.. 22 | 23 | npm install 24 | 25 | # Install and link samples 26 | if [ -f samples/package.json ]; then 27 | cd samples/ 28 | npm link ../ 29 | npm install 30 | cd .. 31 | fi 32 | 33 | npm run lint 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest an idea for this library 3 | labels: 4 | - feature request 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: > 9 | **PLEASE READ**: If you have a support contract with Google, please 10 | create an issue in the [support 11 | console](https://cloud.google.com/support/) instead of filing on GitHub. 12 | This will ensure a timely response. Otherwise, please make sure to 13 | follow the steps below. 14 | - type: textarea 15 | attributes: 16 | label: > 17 | What would you like to see in the library? 18 | description: > 19 | Screenshots can be provided in the issue body below. 20 | placeholder: | 21 | 1. Set up authentication like so 22 | 2. Run the program like so 23 | 3. X would be nice to happen 24 | - type: textarea 25 | attributes: 26 | label: Describe alternatives you've considered 27 | - type: textarea 28 | attributes: 29 | label: Additional context/notes 30 | -------------------------------------------------------------------------------- /.github/workflows/response.yaml: -------------------------------------------------------------------------------- 1 | name: no_response 2 | on: 3 | schedule: 4 | - cron: '30 1 * * *' # Run every day at 01:30 5 | workflow_dispatch: 6 | issue_comment: 7 | 8 | jobs: 9 | close: 10 | if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' 11 | runs-on: ubuntu-latest 12 | permissions: 13 | issues: write 14 | pull-requests: write 15 | steps: 16 | - uses: actions/checkout@v4 17 | - uses: actions/github-script@v7 18 | with: 19 | script: | 20 | const script = require('./.github/scripts/close-unresponsive.cjs') 21 | await script({github, context}) 22 | 23 | remove_label: 24 | if: github.event_name == 'issue_comment' 25 | runs-on: ubuntu-latest 26 | permissions: 27 | issues: write 28 | pull-requests: write 29 | steps: 30 | - uses: actions/checkout@v4 31 | - uses: actions/github-script@v7 32 | with: 33 | script: | 34 | const script = require('./.github/scripts/remove-response-label.cjs') 35 | await script({github, context}) 36 | -------------------------------------------------------------------------------- /.kokoro/test.bat: -------------------------------------------------------------------------------- 1 | @rem Copyright 2018 Google LLC. All rights reserved. 2 | @rem 3 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 4 | @rem you may not use this file except in compliance with the License. 5 | @rem You may obtain a copy of the License at 6 | @rem 7 | @rem http://www.apache.org/licenses/LICENSE-2.0 8 | @rem 9 | @rem Unless required by applicable law or agreed to in writing, software 10 | @rem distributed under the License is distributed on an "AS IS" BASIS, 11 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | @rem See the License for the specific language governing permissions and 13 | @rem limitations under the License. 14 | 15 | @echo "Starting Windows build" 16 | 17 | cd /d %~dp0 18 | cd .. 19 | 20 | @rem npm path is not currently set in our image, we should fix this next time 21 | @rem we upgrade Node.js in the image: 22 | SET PATH=%PATH%;/cygdrive/c/Program Files/nodejs/npm 23 | 24 | call nvm use v14.17.3 25 | call which node 26 | 27 | call npm install || goto :error 28 | call npm run test || goto :error 29 | 30 | goto :EOF 31 | 32 | :error 33 | exit /b 1 34 | -------------------------------------------------------------------------------- /.kokoro/release/docs-devsite.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2021 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -eo pipefail 18 | 19 | if [[ -z "$CREDENTIALS" ]]; then 20 | # if CREDENTIALS are explicitly set, assume we're testing locally 21 | # and don't set NPM_CONFIG_PREFIX. 22 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global 23 | export PATH="$PATH:${NPM_CONFIG_PREFIX}/bin" 24 | cd $(dirname $0)/../.. 25 | fi 26 | 27 | npm install 28 | npm install --no-save @google-cloud/cloud-rad@^0.4.0 29 | # publish docs to devsite 30 | npx @google-cloud/cloud-rad . cloud-rad 31 | -------------------------------------------------------------------------------- /.mocharc.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | const config = { 15 | "enable-source-maps": true, 16 | "throw-deprecation": true, 17 | "timeout": 10000, 18 | "recursive": true 19 | } 20 | if (process.env.MOCHA_THROW_DEPRECATION === 'false') { 21 | delete config['throw-deprecation']; 22 | } 23 | if (process.env.MOCHA_REPORTER) { 24 | config.reporter = process.env.MOCHA_REPORTER; 25 | } 26 | if (process.env.MOCHA_REPORTER_OUTPUT) { 27 | config['reporter-option'] = `output=${process.env.MOCHA_REPORTER_OUTPUT}`; 28 | } 29 | module.exports = config 30 | -------------------------------------------------------------------------------- /.kokoro/trampoline.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2017 Google Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # This file is not used any more, but we keep this file for making it 17 | # easy to roll back. 18 | # TODO: Remove this file from the template. 19 | 20 | set -eo pipefail 21 | 22 | # Always run the cleanup script, regardless of the success of bouncing into 23 | # the container. 24 | function cleanup() { 25 | chmod +x ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh 26 | ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh 27 | echo "cleanup"; 28 | } 29 | trap cleanup EXIT 30 | 31 | $(dirname $0)/populate-secrets.sh # Secret Manager secrets. 32 | python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py" 33 | -------------------------------------------------------------------------------- /.github/generated-files-bot.yml: -------------------------------------------------------------------------------- 1 | generatedFiles: 2 | - path: '.kokoro/**' 3 | message: '`.kokoro` files are templated and should be updated in [`synthtool`](https://github.com/googleapis/synthtool)' 4 | - path: '.github/CODEOWNERS' 5 | message: 'CODEOWNERS should instead be modified via the `codeowner_team` property in .repo-metadata.json' 6 | - path: '.github/workflows/ci.yaml' 7 | message: '`.github/workflows/ci.yaml` (GitHub Actions) should be updated in [`synthtool`](https://github.com/googleapis/synthtool)' 8 | - path: '.github/generated-files-bot.+(yml|yaml)' 9 | message: '`.github/generated-files-bot.(yml|yaml)` should be updated in [`synthtool`](https://github.com/googleapis/synthtool)' 10 | - path: 'README.md' 11 | message: '`README.md` is managed by [`synthtool`](https://github.com/googleapis/synthtool). However, a partials file can be used to update the README, e.g.: https://github.com/googleapis/nodejs-storage/blob/main/.readme-partials.yaml' 12 | - path: 'samples/README.md' 13 | message: '`samples/README.md` is managed by [`synthtool`](https://github.com/googleapis/synthtool). However, a partials file can be used to update the README, e.g.: https://github.com/googleapis/nodejs-storage/blob/main/.readme-partials.yaml' 14 | ignoreAuthors: 15 | - 'gcf-owl-bot[bot]' 16 | - 'yoshi-automation' 17 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | > Thank you for opening a Pull Request! Before submitting your PR, there are a few things you can do to make sure it goes smoothly: 2 | 3 | ## Description 4 | 5 | > Please provide a detailed description for the change. 6 | > As much as possible, please try to keep changes separate by purpose. For example, try not to make a one-line bug fix in a feature request, or add an irrelevant README change to a bug fix. 7 | 8 | ## Impact 9 | 10 | > What's the impact of this change? 11 | 12 | ## Testing 13 | 14 | > Have you added unit and integration tests if necessary? 15 | > Were any tests changed? Are any breaking changes necessary? 16 | 17 | ## Additional Information 18 | 19 | > Any additional details that we should be aware of? 20 | 21 | ## Checklist 22 | 23 | - [ ] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/nodejs-paginator/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea 24 | - [ ] Ensure the tests and linter pass 25 | - [ ] Code coverage does not decrease 26 | - [ ] Appropriate docs were updated 27 | - [ ] Appropriate comments were added, particularly in complex areas or places that require background 28 | - [ ] No new warnings or issues will be generated from this change 29 | 30 | Fixes #issue_number_goes_here 🦕 31 | -------------------------------------------------------------------------------- /.github/scripts/remove-response-label.cjs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | module.exports = async ({ github, context }) => { 16 | const commenter = context.actor; 17 | const issue = await github.rest.issues.get({ 18 | owner: context.repo.owner, 19 | repo: context.repo.repo, 20 | issue_number: context.issue.number, 21 | }); 22 | const author = issue.data.user.login; 23 | const labels = issue.data.labels.map((e) => e.name); 24 | 25 | if (author === commenter && labels.includes('needs more info')) { 26 | await github.rest.issues.removeLabel({ 27 | owner: context.repo.owner, 28 | repo: context.repo.repo, 29 | issue_number: context.issue.number, 30 | name: 'needs more info', 31 | }); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /.jsdoc.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | 'use strict'; 17 | 18 | module.exports = { 19 | opts: { 20 | readme: './README.md', 21 | package: './package.json', 22 | template: './node_modules/jsdoc-fresh', 23 | recurse: true, 24 | verbose: true, 25 | destination: './docs/' 26 | }, 27 | plugins: [ 28 | 'plugins/markdown', 29 | 'jsdoc-region-tag' 30 | ], 31 | source: { 32 | excludePattern: '(^|\\/|\\\\)[._]', 33 | include: [ 34 | 'build/src', 35 | ], 36 | includePattern: '\\.js$' 37 | }, 38 | templates: { 39 | copyright: 'Copyright 2019 Google, LLC.', 40 | includeDate: false, 41 | sourceFiles: false, 42 | systemName: '@google-cloud/paginator', 43 | theme: 'lumen', 44 | default: { 45 | outputSourceFiles: false 46 | } 47 | }, 48 | markdown: { 49 | idInHeadings: true 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /.kokoro/release/publish.cfg: -------------------------------------------------------------------------------- 1 | before_action { 2 | fetch_keystore { 3 | keystore_resource { 4 | keystore_config_id: 73713 5 | keyname: "docuploader_service_account" 6 | } 7 | } 8 | } 9 | 10 | before_action { 11 | fetch_keystore { 12 | keystore_resource { 13 | keystore_config_id: 73713 14 | keyname: "google-cloud-npm-token-1" 15 | } 16 | } 17 | } 18 | 19 | env_vars: { 20 | key: "SECRET_MANAGER_KEYS" 21 | value: "releasetool-publish-reporter-app,releasetool-publish-reporter-googleapis-installation,releasetool-publish-reporter-pem" 22 | } 23 | 24 | # Download trampoline resources. 25 | gfile_resources: "/bigstore/cloud-devrel-kokoro-resources/trampoline" 26 | 27 | # Use the trampoline script to run in docker. 28 | build_file: "nodejs-paginator/.kokoro/trampoline_v2.sh" 29 | 30 | # Configure the docker image for kokoro-trampoline. 31 | env_vars: { 32 | key: "TRAMPOLINE_IMAGE" 33 | value: "gcr.io/cloud-devrel-kokoro-resources/node:18-user" 34 | } 35 | 36 | env_vars: { 37 | key: "TRAMPOLINE_BUILD_FILE" 38 | value: "github/nodejs-paginator/.kokoro/publish.sh" 39 | } 40 | 41 | # Store the packages we uploaded to npmjs.org and their corresponding 42 | # package-lock.jsons in Placer. That way, we have a record of exactly 43 | # what we published, and which version of which tools we used to publish 44 | # it, which we can use to generate SBOMs and attestations. 45 | action { 46 | define_artifacts { 47 | regex: "github/**/*.tgz" 48 | regex: "github/**/package-lock.json" 49 | strip_prefix: "github" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.trampolinerc: -------------------------------------------------------------------------------- 1 | # Copyright 2020 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Template for .trampolinerc 16 | 17 | # Add required env vars here. 18 | required_envvars+=( 19 | ) 20 | 21 | # Add env vars which are passed down into the container here. 22 | pass_down_envvars+=( 23 | "AUTORELEASE_PR" 24 | "VERSION" 25 | ) 26 | 27 | # Prevent unintentional override on the default image. 28 | if [[ "${TRAMPOLINE_IMAGE_UPLOAD:-false}" == "true" ]] && \ 29 | [[ -z "${TRAMPOLINE_IMAGE:-}" ]]; then 30 | echo "Please set TRAMPOLINE_IMAGE if you want to upload the Docker image." 31 | exit 1 32 | fi 33 | 34 | # Define the default value if it makes sense. 35 | if [[ -z "${TRAMPOLINE_IMAGE_UPLOAD:-}" ]]; then 36 | TRAMPOLINE_IMAGE_UPLOAD="" 37 | fi 38 | 39 | if [[ -z "${TRAMPOLINE_IMAGE:-}" ]]; then 40 | TRAMPOLINE_IMAGE="" 41 | fi 42 | 43 | if [[ -z "${TRAMPOLINE_DOCKERFILE:-}" ]]; then 44 | TRAMPOLINE_DOCKERFILE="" 45 | fi 46 | 47 | if [[ -z "${TRAMPOLINE_BUILD_FILE:-}" ]]; then 48 | TRAMPOLINE_BUILD_FILE="" 49 | fi 50 | 51 | # Secret Manager secrets. 52 | source ${PROJECT_ROOT}/.kokoro/populate-secrets.sh 53 | -------------------------------------------------------------------------------- /.kokoro/publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -eo pipefail 18 | 19 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global 20 | 21 | # Start the releasetool reporter 22 | python3 -m releasetool publish-reporter-script > /tmp/publisher-script; source /tmp/publisher-script 23 | 24 | cd $(dirname $0)/.. 25 | 26 | NPM_TOKEN=$(cat $KOKORO_KEYSTORE_DIR/73713_google-cloud-npm-token-1) 27 | echo "//wombat-dressing-room.appspot.com/:_authToken=${NPM_TOKEN}" > ~/.npmrc 28 | 29 | npm install 30 | npm pack . 31 | # npm provides no way to specify, observe, or predict the name of the tarball 32 | # file it generates. We have to look in the current directory for the freshest 33 | # .tgz file. 34 | TARBALL=$(ls -1 -t *.tgz | head -1) 35 | 36 | npm publish --access=public --registry=https://wombat-dressing-room.appspot.com "$TARBALL" 37 | 38 | # Kokoro collects *.tgz and package-lock.json files and stores them in Placer 39 | # so we can generate SBOMs and attestations. 40 | # However, we *don't* want Kokoro to collect package-lock.json and *.tgz files 41 | # that happened to be installed with dependencies. 42 | find node_modules -name package-lock.json -o -name "*.tgz" | xargs rm -f -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@google-cloud/paginator", 3 | "version": "6.0.0", 4 | "description": "A result paging utility used by Google node.js modules", 5 | "main": "build/src/index.js", 6 | "types": "build/src/index.d.ts", 7 | "repository": "googleapis/nodejs-paginator", 8 | "scripts": { 9 | "test": "c8 mocha build/test", 10 | "compile": "tsc -p .", 11 | "fix": "gts fix", 12 | "prelint": "cd samples; npm link ../; npm install", 13 | "lint": "gts check", 14 | "prepare": "npm run compile", 15 | "pretest": "npm run compile", 16 | "docs": "jsdoc -c .jsdoc.js", 17 | "presystem-test": "npm run compile", 18 | "samples-test": "cd samples/ && npm link ../ && npm test && cd ../", 19 | "system-test": "mocha build/system-test", 20 | "docs-test": "linkinator docs", 21 | "predocs-test": "npm run docs", 22 | "clean": "gts clean", 23 | "precompile": "gts clean" 24 | }, 25 | "keywords": [], 26 | "files": [ 27 | "build/src", 28 | "!build/src/**/*.map" 29 | ], 30 | "author": "Google Inc.", 31 | "license": "Apache-2.0", 32 | "devDependencies": { 33 | "@types/extend": "^3.0.4", 34 | "@types/mocha": "^10.0.10", 35 | "@types/node": "^22.13.8", 36 | "@types/proxyquire": "^1.3.31", 37 | "@types/sinon": "^17.0.4", 38 | "@types/uuid": "^10.0.0", 39 | "c8": "^10.1.3", 40 | "codecov": "^3.8.3", 41 | "gts": "^6.0.2", 42 | "jsdoc": "^4.0.4", 43 | "jsdoc-fresh": "^3.0.0", 44 | "jsdoc-region-tag": "^3.0.0", 45 | "linkinator": "^6.1.2", 46 | "mocha": "^11.1.0", 47 | "path-to-regexp": "^8.2.0", 48 | "proxyquire": "^2.1.3", 49 | "sinon": "^19.0.2", 50 | "typescript": "^5.8.2", 51 | "uuid": "^11.1.0" 52 | }, 53 | "dependencies": { 54 | "extend": "^3.0.2" 55 | }, 56 | "engines": { 57 | "node": ">=18" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /.kokoro/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -eo pipefail 18 | 19 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global 20 | 21 | cd $(dirname $0)/.. 22 | 23 | npm install 24 | # If tests are running against main branch, configure flakybot 25 | # to open issues on failures: 26 | if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"continuous"* ]] || [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"nightly"* ]]; then 27 | export MOCHA_REPORTER_OUTPUT=test_output_sponge_log.xml 28 | export MOCHA_REPORTER=xunit 29 | cleanup() { 30 | chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot 31 | $KOKORO_GFILE_DIR/linux_amd64/flakybot 32 | } 33 | trap cleanup EXIT HUP 34 | fi 35 | # Unit tests exercise the entire API surface, which may include 36 | # deprecation warnings: 37 | export MOCHA_THROW_DEPRECATION=false 38 | npm test 39 | 40 | # codecov combines coverage across integration and unit tests. Include 41 | # the logic below for any environment you wish to collect coverage for: 42 | COVERAGE_NODE=14 43 | if npx check-node-version@3.3.0 --silent --node $COVERAGE_NODE; then 44 | NYC_BIN=./node_modules/nyc/bin/nyc.js 45 | if [ -f "$NYC_BIN" ]; then 46 | $NYC_BIN report || true 47 | fi 48 | bash $KOKORO_GFILE_DIR/codecov.sh 49 | else 50 | echo "coverage is only reported for Node $COVERAGE_NODE" 51 | fi 52 | -------------------------------------------------------------------------------- /.kokoro/release/docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2019 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -eo pipefail 18 | 19 | # build jsdocs (Python is installed on the Node 18 docker image). 20 | if [[ -z "$CREDENTIALS" ]]; then 21 | # if CREDENTIALS are explicitly set, assume we're testing locally 22 | # and don't set NPM_CONFIG_PREFIX. 23 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global 24 | export PATH="$PATH:${NPM_CONFIG_PREFIX}/bin" 25 | cd $(dirname $0)/../.. 26 | fi 27 | npm install 28 | npm run docs 29 | 30 | # create docs.metadata, based on package.json and .repo-metadata.json. 31 | npm i json@9.0.6 -g 32 | python3 -m docuploader create-metadata \ 33 | --name=$(cat .repo-metadata.json | json name) \ 34 | --version=$(cat package.json | json version) \ 35 | --language=$(cat .repo-metadata.json | json language) \ 36 | --distribution-name=$(cat .repo-metadata.json | json distribution_name) \ 37 | --product-page=$(cat .repo-metadata.json | json product_documentation) \ 38 | --github-repository=$(cat .repo-metadata.json | json repo) \ 39 | --issue-tracker=$(cat .repo-metadata.json | json issue_tracker) 40 | cp docs.metadata ./docs/docs.metadata 41 | 42 | # deploy the docs. 43 | if [[ -z "$CREDENTIALS" ]]; then 44 | CREDENTIALS=${KOKORO_KEYSTORE_DIR}/73713_docuploader_service_account 45 | fi 46 | if [[ -z "$BUCKET" ]]; then 47 | BUCKET=docs-staging 48 | fi 49 | python3 -m docuploader upload ./docs --credentials $CREDENTIALS --staging-bucket $BUCKET 50 | -------------------------------------------------------------------------------- /samples/README.md: -------------------------------------------------------------------------------- 1 | [//]: # "This README.md file is auto-generated, all changes to this file will be lost." 2 | [//]: # "To regenerate it, use `python -m synthtool`." 3 | Google Cloud Platform logo 4 | 5 | # [Google Cloud Common Paginator: Node.js Samples](https://github.com/googleapis/nodejs-paginator) 6 | 7 | [![Open in Cloud Shell][shell_img]][shell_link] 8 | 9 | 10 | 11 | ## Table of Contents 12 | 13 | * [Before you begin](#before-you-begin) 14 | * [Samples](#samples) 15 | * [Quickstart](#quickstart) 16 | * [Streamify](#streamify) 17 | 18 | ## Before you begin 19 | 20 | Before running the samples, make sure you've followed the steps outlined in 21 | [Using the client library](https://github.com/googleapis/nodejs-paginator#using-the-client-library). 22 | 23 | `cd samples` 24 | 25 | `npm install` 26 | 27 | `cd ..` 28 | 29 | ## Samples 30 | 31 | 32 | 33 | ### Quickstart 34 | 35 | View the [source code](https://github.com/googleapis/nodejs-paginator/blob/main/samples/quickstart.js). 36 | 37 | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-paginator&page=editor&open_in_editor=samples/quickstart.js,samples/README.md) 38 | 39 | __Usage:__ 40 | 41 | 42 | `node samples/quickstart.js` 43 | 44 | 45 | ----- 46 | 47 | 48 | 49 | 50 | ### Streamify 51 | 52 | View the [source code](https://github.com/googleapis/nodejs-paginator/blob/main/samples/streamify.js). 53 | 54 | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-paginator&page=editor&open_in_editor=samples/streamify.js,samples/README.md) 55 | 56 | __Usage:__ 57 | 58 | 59 | `node samples/streamify.js` 60 | 61 | 62 | 63 | 64 | 65 | 66 | [shell_img]: https://gstatic.com/cloudssh/images/open-btn.png 67 | [shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-paginator&page=editor&open_in_editor=samples/README.md 68 | [product-docs]: 69 | -------------------------------------------------------------------------------- /.kokoro/system-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -eo pipefail 18 | 19 | export NPM_CONFIG_PREFIX=${HOME}/.npm-global 20 | 21 | # Setup service account credentials. 22 | export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/secret_manager/long-door-651-kokoro-system-test-service-account 23 | export GCLOUD_PROJECT=long-door-651 24 | 25 | cd $(dirname $0)/.. 26 | 27 | # Run a pre-test hook, if a pre-system-test.sh is in the project 28 | if [ -f .kokoro/pre-system-test.sh ]; then 29 | set +x 30 | . .kokoro/pre-system-test.sh 31 | set -x 32 | fi 33 | 34 | npm install 35 | 36 | # If tests are running against main branch, configure flakybot 37 | # to open issues on failures: 38 | if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"continuous"* ]] || [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"nightly"* ]]; then 39 | export MOCHA_REPORTER_OUTPUT=test_output_sponge_log.xml 40 | export MOCHA_REPORTER=xunit 41 | cleanup() { 42 | chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot 43 | $KOKORO_GFILE_DIR/linux_amd64/flakybot 44 | } 45 | trap cleanup EXIT HUP 46 | fi 47 | 48 | npm run system-test 49 | 50 | # codecov combines coverage across integration and unit tests. Include 51 | # the logic below for any environment you wish to collect coverage for: 52 | COVERAGE_NODE=14 53 | if npx check-node-version@3.3.0 --silent --node $COVERAGE_NODE; then 54 | NYC_BIN=./node_modules/nyc/bin/nyc.js 55 | if [ -f "$NYC_BIN" ]; then 56 | $NYC_BIN report || true 57 | fi 58 | bash $KOKORO_GFILE_DIR/codecov.sh 59 | else 60 | echo "coverage is only reported for Node $COVERAGE_NODE" 61 | fi 62 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | pull_request: 6 | name: ci 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | node: [18, 20, 22] 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-node@v4 16 | with: 17 | node-version: ${{ matrix.node }} 18 | - run: node --version 19 | # The first installation step ensures that all of our production 20 | # dependencies work on the given Node.js version, this helps us find 21 | # dependencies that don't match our engines field: 22 | - run: npm install --production --engine-strict --ignore-scripts --no-package-lock 23 | # Clean up the production install, before installing dev/production: 24 | - run: rm -rf node_modules 25 | - run: npm install --engine-strict 26 | - run: npm test 27 | env: 28 | MOCHA_THROW_DEPRECATION: false 29 | test-script: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v4 33 | - uses: actions/setup-node@v4 34 | with: 35 | node-version: 18 36 | - run: node --version 37 | - run: npm install --engine-strict 38 | working-directory: .github/scripts 39 | - run: npm test 40 | working-directory: .github/scripts 41 | env: 42 | MOCHA_THROW_DEPRECATION: false 43 | windows: 44 | runs-on: windows-latest 45 | steps: 46 | - uses: actions/checkout@v4 47 | - uses: actions/setup-node@v4 48 | with: 49 | node-version: 18 50 | - run: npm install --engine-strict 51 | - run: npm test 52 | env: 53 | MOCHA_THROW_DEPRECATION: false 54 | lint: 55 | runs-on: ubuntu-latest 56 | steps: 57 | - uses: actions/checkout@v4 58 | - uses: actions/setup-node@v4 59 | with: 60 | node-version: 18 61 | - run: npm install 62 | - run: npm run lint 63 | docs: 64 | runs-on: ubuntu-latest 65 | steps: 66 | - uses: actions/checkout@v4 67 | - uses: actions/setup-node@v4 68 | with: 69 | node-version: 18 70 | - run: npm install 71 | - run: npm run docs 72 | - uses: JustinBeckwith/linkinator-action@v1 73 | with: 74 | paths: docs/ 75 | -------------------------------------------------------------------------------- /.github/scripts/close-invalid-link.cjs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | async function closeIssue(github, owner, repo, number) { 16 | await github.rest.issues.createComment({ 17 | owner: owner, 18 | repo: repo, 19 | issue_number: number, 20 | body: 'Issue was opened with an invalid reproduction link. Please make sure the repository is a valid, publicly-accessible github repository, and make sure the url is complete (example: https://github.com/googleapis/google-cloud-node)' 21 | }); 22 | await github.rest.issues.update({ 23 | owner: owner, 24 | repo: repo, 25 | issue_number: number, 26 | state: 'closed' 27 | }); 28 | } 29 | module.exports = async ({github, context}) => { 30 | const owner = context.repo.owner; 31 | const repo = context.repo.repo; 32 | const number = context.issue.number; 33 | 34 | const issue = await github.rest.issues.get({ 35 | owner: owner, 36 | repo: repo, 37 | issue_number: number, 38 | }); 39 | 40 | const isBugTemplate = issue.data.body.includes('Link to the code that reproduces this issue'); 41 | 42 | if (isBugTemplate) { 43 | console.log(`Issue ${number} is a bug template`) 44 | try { 45 | const link = issue.data.body.split('\n')[18].match(/(https?:\/\/(gist\.)?github.com\/.*)/)[0]; 46 | console.log(`Issue ${number} contains this link: ${link}`) 47 | const isValidLink = (await fetch(link)).ok; 48 | console.log(`Issue ${number} has a ${isValidLink ? 'valid' : 'invalid'} link`) 49 | if (!isValidLink) { 50 | await closeIssue(github, owner, repo, number); 51 | } 52 | } catch (err) { 53 | await closeIssue(github, owner, repo, number); 54 | } 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /samples/streamify.js: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // [START paginator_streamify] 16 | const {paginator} = require('@google-cloud/paginator'); 17 | 18 | // Let's assume the ApiCaller class calls some real API. 19 | // The call() methods accepts a query object and a callback; 20 | // the callback is called with three parameters: 21 | // - error or null, 22 | // - a list of responses, 23 | // - next query to retrieve the next batch of responses. 24 | // This is similar to BigQuery API. 25 | class ApiCaller { 26 | constructor() { 27 | // We use `streamify` to convert the results to a stream. 28 | this.call = paginator.streamify('call_'); 29 | } 30 | 31 | call_(query, callback) { 32 | console.log(`Called call_(${JSON.stringify(query)})`); 33 | 34 | const values = []; 35 | let nextQuery = null; 36 | 37 | if (query.query < 10) { 38 | // Generate ten values 39 | for (let idx = 0; idx < 10; ++idx) { 40 | values.push(query.query * 10 + idx); 41 | } 42 | 43 | // Query to get the next page 44 | nextQuery = {query: query.query + 1}; 45 | } 46 | 47 | // Timeout of 500ms to pretend we're making a network request :) 48 | setTimeout(callback, 500, null, values, nextQuery); 49 | } 50 | } 51 | 52 | // In this sample, we show how the streamified method works. 53 | // We stop streaming when we receive the specific result, 54 | // you can see that it stops requesting new pages after that. 55 | function streamifySample() { 56 | const apiCaller = new ApiCaller(); 57 | const stream = apiCaller.call({query: 0}); 58 | stream.on('data', data => { 59 | console.log(data); 60 | if (data === 30) { 61 | stream.end(); 62 | } 63 | }); 64 | stream.on('end', () => { 65 | console.log('Streaming finished'); 66 | }); 67 | } 68 | // [END paginator_streamify] 69 | 70 | streamifySample(); 71 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation_request.yml: -------------------------------------------------------------------------------- 1 | name: Documentation Requests 2 | description: Requests for more information 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: > 7 | Please use this issue type to log documentation requests against the library itself. 8 | These requests should involve documentation on Github (`.md` files), and should relate to the library 9 | itself. If you have questions or documentation requests for an API, please 10 | reach out to the API tracker itself. 11 | 12 | Please submit an issue to the API team, either by submitting an 13 | issue in its issue tracker https://cloud.google.com/support/docs/issue-trackers), or by 14 | submitting an issue in its linked tracker in the .repo-metadata.json 15 | file in the API under packages/* ([example]()). 16 | You can also submit a request to documentation on cloud.google.com itself with the "Send Feedback" 17 | on the bottom of the page. 18 | 19 | 20 | Please note that documentation requests and questions for specific APIs 21 | will be closed. 22 | - type: checkboxes 23 | attributes: 24 | label: Please make sure you have searched for information in the following 25 | guides. 26 | options: 27 | - label: "Search the issues already opened: 28 | https://github.com/GoogleCloudPlatform/google-cloud-node/issues" 29 | required: true 30 | - label: "Check our Troubleshooting guide: 31 | https://googlecloudplatform.github.io/google-cloud-node/#/docs/guid\ 32 | es/troubleshooting" 33 | required: true 34 | - label: "Check our FAQ: 35 | https://googlecloudplatform.github.io/google-cloud-node/#/docs/guid\ 36 | es/faq" 37 | required: true 38 | - label: "Check our libraries HOW-TO: 39 | https://github.com/googleapis/gax-nodejs/blob/main/client-libraries\ 40 | .md" 41 | required: true 42 | - label: "Check out our authentication guide: 43 | https://github.com/googleapis/google-auth-library-nodejs" 44 | required: true 45 | - label: "Check out handwritten samples for many of our APIs: 46 | https://github.com/GoogleCloudPlatform/nodejs-docs-samples" 47 | required: true 48 | - type: textarea 49 | attributes: 50 | label: > 51 | Documentation Request 52 | validations: 53 | required: true 54 | -------------------------------------------------------------------------------- /.github/scripts/close-unresponsive.cjs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | function labeledEvent(data) { 16 | return data.event === 'labeled' && data.label.name === 'needs more info'; 17 | } 18 | 19 | const numberOfDaysLimit = 15; 20 | const close_message = `This has been closed since a request for information has \ 21 | not been answered for ${numberOfDaysLimit} days. It can be reopened when the \ 22 | requested information is provided.`; 23 | 24 | module.exports = async ({github, context}) => { 25 | const owner = context.repo.owner; 26 | const repo = context.repo.repo; 27 | 28 | const issues = await github.rest.issues.listForRepo({ 29 | owner: owner, 30 | repo: repo, 31 | labels: 'needs more info', 32 | }); 33 | const numbers = issues.data.map((e) => e.number); 34 | 35 | for (const number of numbers) { 36 | const events = await github.paginate( 37 | github.rest.issues.listEventsForTimeline, 38 | { 39 | owner: owner, 40 | repo: repo, 41 | issue_number: number, 42 | }, 43 | (response) => response.data.filter(labeledEvent) 44 | ); 45 | 46 | const latest_response_label = events[events.length - 1]; 47 | 48 | const created_at = new Date(latest_response_label.created_at); 49 | const now = new Date(); 50 | const diff = now - created_at; 51 | const diffDays = diff / (1000 * 60 * 60 * 24); 52 | 53 | if (diffDays > numberOfDaysLimit) { 54 | await github.rest.issues.update({ 55 | owner: owner, 56 | repo: repo, 57 | issue_number: number, 58 | state: 'closed', 59 | }); 60 | 61 | await github.rest.issues.createComment({ 62 | owner: owner, 63 | repo: repo, 64 | issue_number: number, 65 | body: close_message, 66 | }); 67 | } 68 | } 69 | }; 70 | -------------------------------------------------------------------------------- /.kokoro/samples-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -eo pipefail 18 | 19 | # Ensure the npm global directory is writable, otherwise rebuild `npm` 20 | mkdir -p $NPM_CONFIG_PREFIX 21 | npm config -g ls || npm i -g npm@`npm --version` 22 | 23 | # Setup service account credentials. 24 | export GOOGLE_APPLICATION_CREDENTIALS=${KOKORO_GFILE_DIR}/secret_manager/long-door-651-kokoro-system-test-service-account 25 | export GCLOUD_PROJECT=long-door-651 26 | 27 | cd $(dirname $0)/.. 28 | 29 | # Run a pre-test hook, if a pre-samples-test.sh is in the project 30 | if [ -f .kokoro/pre-samples-test.sh ]; then 31 | set +x 32 | . .kokoro/pre-samples-test.sh 33 | set -x 34 | fi 35 | 36 | if [ -f samples/package.json ]; then 37 | npm install 38 | 39 | # Install and link samples 40 | cd samples/ 41 | npm link ../ 42 | npm install 43 | cd .. 44 | # If tests are running against main branch, configure flakybot 45 | # to open issues on failures: 46 | if [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"continuous"* ]] || [[ $KOKORO_BUILD_ARTIFACTS_SUBDIR = *"nightly"* ]]; then 47 | export MOCHA_REPORTER_OUTPUT=test_output_sponge_log.xml 48 | export MOCHA_REPORTER=xunit 49 | cleanup() { 50 | chmod +x $KOKORO_GFILE_DIR/linux_amd64/flakybot 51 | $KOKORO_GFILE_DIR/linux_amd64/flakybot 52 | } 53 | trap cleanup EXIT HUP 54 | fi 55 | 56 | npm run samples-test 57 | fi 58 | 59 | # codecov combines coverage across integration and unit tests. Include 60 | # the logic below for any environment you wish to collect coverage for: 61 | COVERAGE_NODE=14 62 | if npx check-node-version@3.3.0 --silent --node $COVERAGE_NODE; then 63 | NYC_BIN=./node_modules/nyc/bin/nyc.js 64 | if [ -f "$NYC_BIN" ]; then 65 | $NYC_BIN report || true 66 | fi 67 | bash $KOKORO_GFILE_DIR/codecov.sh 68 | else 69 | echo "coverage is only reported for Node $COVERAGE_NODE" 70 | fi 71 | -------------------------------------------------------------------------------- /.kokoro/populate-secrets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Copyright 2020 Google LLC. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # This file is called in the early stage of `trampoline_v2.sh` to 17 | # populate secrets needed for the CI builds. 18 | 19 | set -eo pipefail 20 | 21 | function now { date +"%Y-%m-%d %H:%M:%S" | tr -d '\n' ;} 22 | function msg { println "$*" >&2 ;} 23 | function println { printf '%s\n' "$(now) $*" ;} 24 | 25 | # Populates requested secrets set in SECRET_MANAGER_KEYS 26 | 27 | # In Kokoro CI builds, we use the service account attached to the 28 | # Kokoro VM. This means we need to setup auth on other CI systems. 29 | # For local run, we just use the gcloud command for retrieving the 30 | # secrets. 31 | 32 | if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then 33 | GCLOUD_COMMANDS=( 34 | "docker" 35 | "run" 36 | "--entrypoint=gcloud" 37 | "--volume=${KOKORO_GFILE_DIR}:${KOKORO_GFILE_DIR}" 38 | "gcr.io/google.com/cloudsdktool/cloud-sdk" 39 | ) 40 | if [[ "${TRAMPOLINE_CI:-}" == "kokoro" ]]; then 41 | SECRET_LOCATION="${KOKORO_GFILE_DIR}/secret_manager" 42 | else 43 | echo "Authentication for this CI system is not implemented yet." 44 | exit 2 45 | # TODO: Determine appropriate SECRET_LOCATION and the GCLOUD_COMMANDS. 46 | fi 47 | else 48 | # For local run, use /dev/shm or temporary directory for 49 | # KOKORO_GFILE_DIR. 50 | if [[ -d "/dev/shm" ]]; then 51 | export KOKORO_GFILE_DIR=/dev/shm 52 | else 53 | export KOKORO_GFILE_DIR=$(mktemp -d -t ci-XXXXXXXX) 54 | fi 55 | SECRET_LOCATION="${KOKORO_GFILE_DIR}/secret_manager" 56 | GCLOUD_COMMANDS=("gcloud") 57 | fi 58 | 59 | msg "Creating folder on disk for secrets: ${SECRET_LOCATION}" 60 | mkdir -p ${SECRET_LOCATION} 61 | 62 | for key in $(echo ${SECRET_MANAGER_KEYS} | sed "s/,/ /g") 63 | do 64 | msg "Retrieving secret ${key}" 65 | "${GCLOUD_COMMANDS[@]}" \ 66 | secrets versions access latest \ 67 | --project cloud-devrel-kokoro-resources \ 68 | --secret $key > \ 69 | "$SECRET_LOCATION/$key" 70 | if [[ $? == 0 ]]; then 71 | msg "Secret written to ${SECRET_LOCATION}/${key}" 72 | else 73 | msg "Error retrieving secret ${key}" 74 | exit 2 75 | fi 76 | done 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to become a contributor and submit your own code 2 | 3 | **Table of contents** 4 | 5 | * [Contributor License Agreements](#contributor-license-agreements) 6 | * [Contributing a patch](#contributing-a-patch) 7 | * [Running the tests](#running-the-tests) 8 | * [Releasing the library](#releasing-the-library) 9 | 10 | ## Contributor License Agreements 11 | 12 | We'd love to accept your sample apps and patches! Before we can take them, we 13 | have to jump a couple of legal hurdles. 14 | 15 | Please fill out either the individual or corporate Contributor License Agreement 16 | (CLA). 17 | 18 | * If you are an individual writing original source code and you're sure you 19 | own the intellectual property, then you'll need to sign an [individual CLA](https://developers.google.com/open-source/cla/individual). 20 | * If you work for a company that wants to allow you to contribute your work, 21 | then you'll need to sign a [corporate CLA](https://developers.google.com/open-source/cla/corporate). 22 | 23 | Follow either of the two links above to access the appropriate CLA and 24 | instructions for how to sign and return it. Once we receive it, we'll be able to 25 | accept your pull requests. 26 | 27 | ## Contributing A Patch 28 | 29 | 1. Submit an issue describing your proposed change to the repo in question. 30 | 1. The repo owner will respond to your issue promptly. 31 | 1. If your proposed change is accepted, and you haven't already done so, sign a 32 | Contributor License Agreement (see details above). 33 | 1. Fork the desired repo, develop and test your code changes. 34 | 1. Ensure that your code adheres to the existing style in the code to which 35 | you are contributing. 36 | 1. Ensure that your code has an appropriate set of tests which all pass. 37 | 1. Title your pull request following [Conventional Commits](https://www.conventionalcommits.org/) styling. 38 | 1. Submit a pull request. 39 | 40 | ### Before you begin 41 | 42 | 1. [Select or create a Cloud Platform project][projects]. 43 | 1. [Set up authentication with a service account][auth] so you can access the 44 | API from your local workstation. 45 | 46 | 47 | ## Running the tests 48 | 49 | 1. [Prepare your environment for Node.js setup][setup]. 50 | 51 | 1. Install dependencies: 52 | 53 | npm install 54 | 55 | 1. Run the tests: 56 | 57 | # Run unit tests. 58 | npm test 59 | 60 | # Run sample integration tests. 61 | npm run samples-test 62 | 63 | # Run all system tests. 64 | npm run system-test 65 | 66 | 1. Lint (and maybe fix) any changes: 67 | 68 | npm run fix 69 | 70 | [setup]: https://cloud.google.com/nodejs/docs/setup 71 | [projects]: https://console.cloud.google.com/project 72 | [billing]: https://support.google.com/cloud/answer/6293499#enable-billing 73 | 74 | [auth]: https://cloud.google.com/docs/authentication/getting-started -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Create a report to help us improve 3 | labels: 4 | - bug 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: > 9 | **PLEASE READ**: If you have a support contract with Google, please 10 | create an issue in the [support 11 | console](https://cloud.google.com/support/) instead of filing on GitHub. 12 | This will ensure a timely response. Otherwise, please make sure to 13 | follow the steps below. 14 | - type: checkboxes 15 | attributes: 16 | label: Please make sure you have searched for information in the following 17 | guides. 18 | options: 19 | - label: "Search the issues already opened: 20 | https://github.com/GoogleCloudPlatform/google-cloud-node/issues" 21 | required: true 22 | - label: "Search StackOverflow: 23 | http://stackoverflow.com/questions/tagged/google-cloud-platform+nod\ 24 | e.js" 25 | required: true 26 | - label: "Check our Troubleshooting guide: 27 | https://github.com/googleapis/google-cloud-node/blob/main/docs/trou\ 28 | bleshooting.md" 29 | required: true 30 | - label: "Check our FAQ: 31 | https://github.com/googleapis/google-cloud-node/blob/main/docs/faq.\ 32 | md" 33 | required: true 34 | - label: "Check our libraries HOW-TO: 35 | https://github.com/googleapis/gax-nodejs/blob/main/client-libraries\ 36 | .md" 37 | required: true 38 | - label: "Check out our authentication guide: 39 | https://github.com/googleapis/google-auth-library-nodejs" 40 | required: true 41 | - label: "Check out handwritten samples for many of our APIs: 42 | https://github.com/GoogleCloudPlatform/nodejs-docs-samples" 43 | required: true 44 | - type: input 45 | attributes: 46 | label: > 47 | Link to the code that reproduces this issue. A link to a **public** Github Repository or gist with a minimal 48 | reproduction. 49 | - type: textarea 50 | attributes: 51 | label: > 52 | A step-by-step description of how to reproduce the issue, based on 53 | the linked reproduction. 54 | description: > 55 | Screenshots can be provided in the issue body below. 56 | placeholder: | 57 | 1. Start the application in development (next dev) 58 | 2. Click X 59 | 3. Y will happen 60 | - type: textarea 61 | attributes: 62 | label: A clear and concise description of what the bug is, and what you 63 | expected to happen. 64 | placeholder: Following the steps from the previous section, I expected A to 65 | happen, but I observed B instead 66 | 67 | - type: textarea 68 | attributes: 69 | label: A clear and concise description WHY you expect this behavior, i.e., was it a recent change, there is documentation that points to this behavior, etc. ** 70 | placeholder: 'Documentation here(link) states that B should happen instead of A' 71 | -------------------------------------------------------------------------------- /.github/scripts/fixtures/invalidIssueBody.txt: -------------------------------------------------------------------------------- 1 | ### Please make sure you have searched for information in the following guides. 2 | 3 | - [X] Search the issues already opened: https://github.com/GoogleCloudPlatform/google-cloud-node/issues 4 | - [X] Search StackOverflow: http://stackoverflow.com/questions/tagged/google-cloud-platform+node.js 5 | - [X] Check our Troubleshooting guide: https://googlecloudplatform.github.io/google-cloud-node/#/docs/guides/troubleshooting 6 | - [X] Check our FAQ: https://googlecloudplatform.github.io/google-cloud-node/#/docs/guides/faq 7 | - [X] Check our libraries HOW-TO: https://github.com/googleapis/gax-nodejs/blob/main/client-libraries.md 8 | - [X] Check out our authentication guide: https://github.com/googleapis/google-auth-library-nodejs 9 | - [X] Check out handwritten samples for many of our APIs: https://github.com/GoogleCloudPlatform/nodejs-docs-samples 10 | 11 | ### A screenshot that you have tested with "Try this API". 12 | 13 | 14 | N/A 15 | 16 | ### Link to the code that reproduces this issue. A link to a **public** Github Repository or gist with a minimal reproduction. 17 | 18 | not-a-link 19 | 20 | ### A step-by-step description of how to reproduce the issue, based on the linked reproduction. 21 | 22 | 23 | Change MY_PROJECT to your project name, add credentials if needed and run. 24 | 25 | ### A clear and concise description of what the bug is, and what you expected to happen. 26 | 27 | The application crashes with the following exception (which there is no way to catch). It should just emit error, and allow graceful handling. 28 | TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be of type string or an instance of Buffer or Uint8Array. Received an instance of Object 29 | at _write (node:internal/streams/writable:474:13) 30 | at Writable.write (node:internal/streams/writable:502:10) 31 | at Duplexify._write (/project/node_modules/duplexify/index.js:212:22) 32 | at doWrite (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:390:139) 33 | at writeOrBuffer (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:381:5) 34 | at Writable.write (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:302:11) 35 | at Pumpify. (/project/node_modules/@google-cloud/speech/build/src/helpers.js:79:27) 36 | at Object.onceWrapper (node:events:633:26) 37 | at Pumpify.emit (node:events:518:28) 38 | at obj. [as _write] (/project/node_modules/stubs/index.js:28:22) 39 | at doWrite (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:390:139) 40 | at writeOrBuffer (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:381:5) 41 | at Writable.write (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:302:11) 42 | at PassThrough.ondata (node:internal/streams/readable:1007:22) 43 | at PassThrough.emit (node:events:518:28) 44 | at addChunk (node:internal/streams/readable:559:12) { 45 | code: 'ERR_INVALID_ARG_TYPE' 46 | 47 | 48 | ### A clear and concise description WHY you expect this behavior, i.e., was it a recent change, there is documentation that points to this behavior, etc. ** 49 | 50 | No library should crash an application this way. -------------------------------------------------------------------------------- /.github/scripts/fixtures/validIssueBody.txt: -------------------------------------------------------------------------------- 1 | ### Please make sure you have searched for information in the following guides. 2 | 3 | - [X] Search the issues already opened: https://github.com/GoogleCloudPlatform/google-cloud-node/issues 4 | - [X] Search StackOverflow: http://stackoverflow.com/questions/tagged/google-cloud-platform+node.js 5 | - [X] Check our Troubleshooting guide: https://googlecloudplatform.github.io/google-cloud-node/#/docs/guides/troubleshooting 6 | - [X] Check our FAQ: https://googlecloudplatform.github.io/google-cloud-node/#/docs/guides/faq 7 | - [X] Check our libraries HOW-TO: https://github.com/googleapis/gax-nodejs/blob/main/client-libraries.md 8 | - [X] Check out our authentication guide: https://github.com/googleapis/google-auth-library-nodejs 9 | - [X] Check out handwritten samples for many of our APIs: https://github.com/GoogleCloudPlatform/nodejs-docs-samples 10 | 11 | ### A screenshot that you have tested with "Try this API". 12 | 13 | 14 | N/A 15 | 16 | ### Link to the code that reproduces this issue. A link to a **public** Github Repository or gist with a minimal reproduction. 17 | 18 | https://gist.github.com/orgads/13cbf44c91923da27d8772b5f10489c9 19 | 20 | ### A step-by-step description of how to reproduce the issue, based on the linked reproduction. 21 | 22 | 23 | Change MY_PROJECT to your project name, add credentials if needed and run. 24 | 25 | ### A clear and concise description of what the bug is, and what you expected to happen. 26 | 27 | The application crashes with the following exception (which there is no way to catch). It should just emit error, and allow graceful handling. 28 | TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be of type string or an instance of Buffer or Uint8Array. Received an instance of Object 29 | at _write (node:internal/streams/writable:474:13) 30 | at Writable.write (node:internal/streams/writable:502:10) 31 | at Duplexify._write (/project/node_modules/duplexify/index.js:212:22) 32 | at doWrite (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:390:139) 33 | at writeOrBuffer (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:381:5) 34 | at Writable.write (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:302:11) 35 | at Pumpify. (/project/node_modules/@google-cloud/speech/build/src/helpers.js:79:27) 36 | at Object.onceWrapper (node:events:633:26) 37 | at Pumpify.emit (node:events:518:28) 38 | at obj. [as _write] (/project/node_modules/stubs/index.js:28:22) 39 | at doWrite (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:390:139) 40 | at writeOrBuffer (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:381:5) 41 | at Writable.write (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:302:11) 42 | at PassThrough.ondata (node:internal/streams/readable:1007:22) 43 | at PassThrough.emit (node:events:518:28) 44 | at addChunk (node:internal/streams/readable:559:12) { 45 | code: 'ERR_INVALID_ARG_TYPE' 46 | 47 | 48 | ### A clear and concise description WHY you expect this behavior, i.e., was it a recent change, there is documentation that points to this behavior, etc. ** 49 | 50 | No library should crash an application this way. -------------------------------------------------------------------------------- /.github/scripts/fixtures/validIssueBodyDifferentLinkLocation.txt: -------------------------------------------------------------------------------- 1 | ### Please make sure you have searched for information in the following guides. 2 | 3 | - [X] Search the issues already opened: https://github.com/GoogleCloudPlatform/google-cloud-node/issues 4 | - [X] Search StackOverflow: http://stackoverflow.com/questions/tagged/google-cloud-platform+node.js 5 | - [X] Check our Troubleshooting guide: https://googlecloudplatform.github.io/google-cloud-node/#/docs/guides/troubleshooting 6 | - [X] Check our FAQ: https://googlecloudplatform.github.io/google-cloud-node/#/docs/guides/faq 7 | - [X] Check our libraries HOW-TO: https://github.com/googleapis/gax-nodejs/blob/main/client-libraries.md 8 | - [X] Check out our authentication guide: https://github.com/googleapis/google-auth-library-nodejs 9 | - [X] Check out handwritten samples for many of our APIs: https://github.com/GoogleCloudPlatform/nodejs-docs-samples 10 | 11 | ### A screenshot that you have tested with "Try this API". 12 | 13 | 14 | N/A 15 | 16 | ### A step-by-step description of how to reproduce the issue, based on the linked reproduction. 17 | 18 | 19 | Change MY_PROJECT to your project name, add credentials if needed and run. 20 | 21 | ### A clear and concise description of what the bug is, and what you expected to happen. 22 | 23 | The application crashes with the following exception (which there is no way to catch). It should just emit error, and allow graceful handling. 24 | TypeError [ERR_INVALID_ARG_TYPE]: The "chunk" argument must be of type string or an instance of Buffer or Uint8Array. Received an instance of Object 25 | at _write (node:internal/streams/writable:474:13) 26 | at Writable.write (node:internal/streams/writable:502:10) 27 | at Duplexify._write (/project/node_modules/duplexify/index.js:212:22) 28 | at doWrite (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:390:139) 29 | at writeOrBuffer (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:381:5) 30 | at Writable.write (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:302:11) 31 | at Pumpify. (/project/node_modules/@google-cloud/speech/build/src/helpers.js:79:27) 32 | at Object.onceWrapper (node:events:633:26) 33 | at Pumpify.emit (node:events:518:28) 34 | at obj. [as _write] (/project/node_modules/stubs/index.js:28:22) 35 | at doWrite (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:390:139) 36 | at writeOrBuffer (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:381:5) 37 | at Writable.write (/project/node_modules/duplexify/node_modules/readable-stream/lib/_stream_writable.js:302:11) 38 | at PassThrough.ondata (node:internal/streams/readable:1007:22) 39 | at PassThrough.emit (node:events:518:28) 40 | at addChunk (node:internal/streams/readable:559:12) { 41 | code: 'ERR_INVALID_ARG_TYPE' 42 | 43 | ### Link to the code that reproduces this issue. A link to a **public** Github Repository with a minimal reproduction. 44 | 45 | 46 | https://gist.github.com/orgads/13cbf44c91923da27d8772b5f10489c9 47 | 48 | ### A clear and concise description WHY you expect this behavior, i.e., was it a recent change, there is documentation that points to this behavior, etc. ** 49 | 50 | No library should crash an application this way. -------------------------------------------------------------------------------- /.github/scripts/tests/close-invalid-link.test.cjs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | const { describe, it } = require('mocha'); 18 | const closeInvalidLink = require('../close-invalid-link.cjs'); 19 | const fs = require('fs'); 20 | const sinon = require('sinon'); 21 | 22 | describe('close issues with invalid links', () => { 23 | let octokitStub; 24 | let issuesStub; 25 | 26 | beforeEach(() => { 27 | issuesStub = { 28 | get: sinon.stub(), 29 | createComment: sinon.stub(), 30 | update: sinon.stub(), 31 | }; 32 | octokitStub = { 33 | rest: { 34 | issues: issuesStub, 35 | }, 36 | }; 37 | }); 38 | 39 | afterEach(() => { 40 | sinon.restore(); 41 | }); 42 | 43 | it('does not do anything if it is not a bug', async () => { 44 | const context = { repo: { owner: 'testOrg', repo: 'testRepo' }, issue: { number: 1 } }; 45 | issuesStub.get.resolves({ data: { body: "I'm having a problem with this." } }); 46 | 47 | await closeInvalidLink({ github: octokitStub, context }); 48 | 49 | sinon.assert.calledOnce(issuesStub.get); 50 | sinon.assert.notCalled(issuesStub.createComment); 51 | sinon.assert.notCalled(issuesStub.update); 52 | }); 53 | 54 | it('does not do anything if it is a bug with an appropriate link', async () => { 55 | const context = { repo: { owner: 'testOrg', repo: 'testRepo' }, issue: { number: 1 } }; 56 | issuesStub.get.resolves({ data: { body: fs.readFileSync('./fixtures/validIssueBody.txt', 'utf-8') } }); 57 | 58 | await closeInvalidLink({ github: octokitStub, context }); 59 | 60 | sinon.assert.calledOnce(issuesStub.get); 61 | sinon.assert.notCalled(issuesStub.createComment); 62 | sinon.assert.notCalled(issuesStub.update); 63 | }); 64 | 65 | it('does not do anything if it is a bug with an appropriate link and the template changes', async () => { 66 | const context = { repo: { owner: 'testOrg', repo: 'testRepo' }, issue: { number: 1 } }; 67 | issuesStub.get.resolves({ data: { body: fs.readFileSync('./fixtures/validIssueBodyDifferentLinkLocation.txt', 'utf-8') } }); 68 | 69 | await closeInvalidLink({ github: octokitStub, context }); 70 | 71 | sinon.assert.calledOnce(issuesStub.get); 72 | sinon.assert.notCalled(issuesStub.createComment); 73 | sinon.assert.notCalled(issuesStub.update); 74 | }); 75 | 76 | it('closes the issue if the link is invalid', async () => { 77 | const context = { repo: { owner: 'testOrg', repo: 'testRepo' }, issue: { number: 1 } }; 78 | issuesStub.get.resolves({ data: { body: fs.readFileSync('./fixtures/invalidIssueBody.txt', 'utf-8') } }); 79 | 80 | await closeInvalidLink({ github: octokitStub, context }); 81 | 82 | sinon.assert.calledOnce(issuesStub.get); 83 | sinon.assert.calledOnce(issuesStub.createComment); 84 | sinon.assert.calledOnce(issuesStub.update); 85 | }); 86 | }); -------------------------------------------------------------------------------- /src/resource-stream.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2019 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import {Transform, Writable} from 'stream'; 18 | import {ParsedArguments} from './'; 19 | 20 | interface ResourceEvents { 21 | addListener(event: 'data', listener: (data: T) => void): this; 22 | emit(event: 'data', data: T): boolean; 23 | on(event: 'data', listener: (data: T) => void): this; 24 | once(event: 'data', listener: (data: T) => void): this; 25 | prependListener(event: 'data', listener: (data: T) => void): this; 26 | prependOnceListener(event: 'data', listener: (data: T) => void): this; 27 | removeListener(event: 'data', listener: (data: T) => void): this; 28 | } 29 | 30 | export class ResourceStream extends Transform implements ResourceEvents { 31 | _ended: boolean; 32 | _maxApiCalls: number; 33 | _nextQuery: {} | null; 34 | _otherArgs: unknown[]; 35 | _reading: boolean; 36 | _requestFn: Function; 37 | _requestsMade: number; 38 | _resultsToSend: number; 39 | constructor(args: ParsedArguments, requestFn: Function) { 40 | const options = Object.assign({objectMode: true}, args.streamOptions); 41 | super(options); 42 | 43 | this._ended = false; 44 | this._maxApiCalls = args.maxApiCalls === -1 ? Infinity : args.maxApiCalls!; 45 | this._nextQuery = args.query!; 46 | this._reading = false; 47 | this._requestFn = requestFn; 48 | this._requestsMade = 0; 49 | this._resultsToSend = args.maxResults === -1 ? Infinity : args.maxResults!; 50 | this._otherArgs = []; 51 | } 52 | /* eslint-disable @typescript-eslint/no-explicit-any */ 53 | end( 54 | ...args: any[] 55 | ): ReturnType extends Writable ? this : void { 56 | this._ended = true; 57 | return super.end(...args); 58 | } 59 | _read() { 60 | if (this._reading) { 61 | return; 62 | } 63 | 64 | this._reading = true; 65 | 66 | // Wrap in a try/catch to catch input linting errors, e.g. 67 | // an invalid BigQuery query. These errors are thrown in an 68 | // async fashion, which makes them un-catchable by the user. 69 | try { 70 | this._requestFn( 71 | this._nextQuery, 72 | ( 73 | err: Error | null, 74 | results: T[], 75 | nextQuery: {} | null, 76 | ...otherArgs: [] 77 | ) => { 78 | if (err) { 79 | this.destroy(err); 80 | return; 81 | } 82 | 83 | this._otherArgs = otherArgs; 84 | this._nextQuery = nextQuery; 85 | 86 | if (this._resultsToSend !== Infinity) { 87 | results = results.splice(0, this._resultsToSend); 88 | this._resultsToSend -= results.length; 89 | } 90 | 91 | let more = true; 92 | 93 | for (const result of results) { 94 | if (this._ended) { 95 | break; 96 | } 97 | more = this.push(result); 98 | } 99 | 100 | const isFinished = !this._nextQuery || this._resultsToSend < 1; 101 | const madeMaxCalls = ++this._requestsMade >= this._maxApiCalls; 102 | 103 | if (isFinished || madeMaxCalls) { 104 | this.end(); 105 | } 106 | 107 | if (more && !this._ended) { 108 | setImmediate(() => this._read()); 109 | } 110 | 111 | this._reading = false; 112 | }, 113 | ); 114 | } catch (e) { 115 | this.destroy(e as Error); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /.github/scripts/tests/close-or-remove-response-label.test.cjs: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 'use strict'; 16 | 17 | const { describe, it, beforeEach, afterEach } = require('mocha'); 18 | const removeResponseLabel = require('../remove-response-label.cjs'); 19 | const closeUnresponsive = require('../close-unresponsive.cjs'); 20 | const sinon = require('sinon'); 21 | 22 | function getISODateDaysAgo(days) { 23 | const today = new Date(); 24 | const daysAgo = new Date(today.setDate(today.getDate() - days)); 25 | return daysAgo.toISOString(); 26 | } 27 | 28 | describe('close issues or remove needs more info labels', () => { 29 | let octokitStub; 30 | let issuesStub; 31 | let paginateStub; 32 | 33 | beforeEach(() => { 34 | issuesStub = { 35 | listForRepo: sinon.stub(), 36 | update: sinon.stub(), 37 | createComment: sinon.stub(), 38 | get: sinon.stub(), 39 | removeLabel: sinon.stub(), 40 | }; 41 | paginateStub = sinon.stub(); 42 | octokitStub = { 43 | rest: { 44 | issues: issuesStub, 45 | }, 46 | paginate: paginateStub, 47 | }; 48 | }); 49 | 50 | afterEach(() => { 51 | sinon.restore(); 52 | }); 53 | 54 | it('closes the issue if the OP has not responded within the allotted time and there is a needs-more-info label', async () => { 55 | const context = { owner: 'testOrg', repo: 'testRepo' }; 56 | const issuesInRepo = [{ user: { login: 'OP' }, labels: [{ name: 'needs more info' }] }]; 57 | const eventsInIssue = [{ event: 'labeled', label: { name: 'needs more info' }, created_at: getISODateDaysAgo(16) }]; 58 | 59 | issuesStub.listForRepo.resolves({ data: issuesInRepo }); 60 | paginateStub.resolves(eventsInIssue); 61 | 62 | await closeUnresponsive({ github: octokitStub, context }); 63 | 64 | sinon.assert.calledOnce(issuesStub.listForRepo); 65 | sinon.assert.calledOnce(paginateStub); 66 | sinon.assert.calledOnce(issuesStub.update); 67 | sinon.assert.calledOnce(issuesStub.createComment); 68 | }); 69 | 70 | it('does nothing if not enough time has passed and there is a needs-more-info label', async () => { 71 | const context = { owner: 'testOrg', repo: 'testRepo' }; 72 | const issuesInRepo = [{ user: { login: 'OP' }, labels: [{ name: 'needs more info' }] }]; 73 | const eventsInIssue = [{ event: 'labeled', label: { name: 'needs more info' }, created_at: getISODateDaysAgo(14) }]; 74 | 75 | issuesStub.listForRepo.resolves({ data: issuesInRepo }); 76 | paginateStub.resolves(eventsInIssue); 77 | 78 | await closeUnresponsive({ github: octokitStub, context }); 79 | 80 | sinon.assert.calledOnce(issuesStub.listForRepo); 81 | sinon.assert.calledOnce(paginateStub); 82 | sinon.assert.notCalled(issuesStub.update); 83 | sinon.assert.notCalled(issuesStub.createComment); 84 | }); 85 | 86 | it('removes the label if OP responded', async () => { 87 | const context = { actor: 'OP', repo: { owner: 'testOrg', repo: 'testRepo' }, issue: { number: 1 } }; 88 | const issueContext = { user: {login: 'OP'}, labels: [{ name: 'needs more info' }] }; 89 | 90 | issuesStub.get.resolves({ data: issueContext }); 91 | 92 | await removeResponseLabel({ github: octokitStub, context }); 93 | 94 | sinon.assert.calledOnce(issuesStub.get); 95 | sinon.assert.calledOnce(issuesStub.removeLabel); 96 | }); 97 | 98 | it('does not remove the label if author responded', async () => { 99 | const context = { actor: 'repo-maintainer', repo: { owner: 'testOrg', repo: 'testRepo' }, issue: { number: 1 } }; 100 | const issueContext = { user: {login: 'OP'}, labels: [{ name: 'needs more info' }] }; 101 | 102 | issuesStub.get.resolves({ data: issueContext }); 103 | 104 | await removeResponseLabel({ github: octokitStub, context }); 105 | 106 | sinon.assert.calledOnce(issuesStub.get); 107 | sinon.assert.notCalled(issuesStub.removeLabel); 108 | }); 109 | }); -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | In the interest of fostering an open and welcoming environment, we as 7 | contributors and maintainers pledge to making participation in our project and 8 | our community a harassment-free experience for everyone, regardless of age, body 9 | size, disability, ethnicity, gender identity and expression, level of 10 | experience, education, socio-economic status, nationality, personal appearance, 11 | race, religion, or sexual identity and orientation. 12 | 13 | ## Our Standards 14 | 15 | Examples of behavior that contributes to creating a positive environment 16 | include: 17 | 18 | * Using welcoming and inclusive language 19 | * Being respectful of differing viewpoints and experiences 20 | * Gracefully accepting constructive criticism 21 | * Focusing on what is best for the community 22 | * Showing empathy towards other community members 23 | 24 | Examples of unacceptable behavior by participants include: 25 | 26 | * The use of sexualized language or imagery and unwelcome sexual attention or 27 | advances 28 | * Trolling, insulting/derogatory comments, and personal or political attacks 29 | * Public or private harassment 30 | * Publishing others' private information, such as a physical or electronic 31 | address, without explicit permission 32 | * Other conduct which could reasonably be considered inappropriate in a 33 | professional setting 34 | 35 | ## Our Responsibilities 36 | 37 | Project maintainers are responsible for clarifying the standards of acceptable 38 | behavior and are expected to take appropriate and fair corrective action in 39 | response to any instances of unacceptable behavior. 40 | 41 | Project maintainers have the right and responsibility to remove, edit, or reject 42 | comments, commits, code, wiki edits, issues, and other contributions that are 43 | not aligned to this Code of Conduct, or to ban temporarily or permanently any 44 | contributor for other behaviors that they deem inappropriate, threatening, 45 | offensive, or harmful. 46 | 47 | ## Scope 48 | 49 | This Code of Conduct applies both within project spaces and in public spaces 50 | when an individual is representing the project or its community. Examples of 51 | representing a project or community include using an official project e-mail 52 | address, posting via an official social media account, or acting as an appointed 53 | representative at an online or offline event. Representation of a project may be 54 | further defined and clarified by project maintainers. 55 | 56 | This Code of Conduct also applies outside the project spaces when the Project 57 | Steward has a reasonable belief that an individual's behavior may have a 58 | negative impact on the project or its community. 59 | 60 | ## Conflict Resolution 61 | 62 | We do not believe that all conflict is bad; healthy debate and disagreement 63 | often yield positive results. However, it is never okay to be disrespectful or 64 | to engage in behavior that violates the project’s code of conduct. 65 | 66 | If you see someone violating the code of conduct, you are encouraged to address 67 | the behavior directly with those involved. Many issues can be resolved quickly 68 | and easily, and this gives people more control over the outcome of their 69 | dispute. If you are unable to resolve the matter for any reason, or if the 70 | behavior is threatening or harassing, report it. We are dedicated to providing 71 | an environment where participants feel welcome and safe. 72 | 73 | Reports should be directed to *googleapis-stewards@google.com*, the 74 | Project Steward(s) for *Google Cloud Client Libraries*. It is the Project Steward’s duty to 75 | receive and address reported violations of the code of conduct. They will then 76 | work with a committee consisting of representatives from the Open Source 77 | Programs Office and the Google Open Source Strategy team. If for any reason you 78 | are uncomfortable reaching out to the Project Steward, please email 79 | opensource@google.com. 80 | 81 | We will investigate every complaint, but you may not receive a direct response. 82 | We will use our discretion in determining when and how to follow up on reported 83 | incidents, which may range from not taking action to permanent expulsion from 84 | the project and project-sponsored spaces. We will notify the accused of the 85 | report and provide them an opportunity to discuss it before any action is taken. 86 | The identity of the reporter will be omitted from the details of the report 87 | supplied to the accused. In potentially harmful situations, such as ongoing 88 | harassment or threats to anyone's safety, we may take action without notice. 89 | 90 | ## Attribution 91 | 92 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, 93 | available at 94 | https://www.contributor-covenant.org/version/1/4/code-of-conduct.html -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [//]: # "This README.md file is auto-generated, all changes to this file will be lost." 2 | [//]: # "To regenerate it, use `python -m synthtool`." 3 | Google Cloud Platform logo 4 | 5 | # [Google Cloud Common Paginator: Node.js Client](https://github.com/googleapis/nodejs-paginator) 6 | 7 | [![release level](https://img.shields.io/badge/release%20level-stable-brightgreen.svg?style=flat)](https://cloud.google.com/terms/launch-stages) 8 | [![npm version](https://img.shields.io/npm/v/@google-cloud/paginator.svg)](https://www.npmjs.org/package/@google-cloud/paginator) 9 | 10 | 11 | 12 | 13 | A result paging utility used by Google node.js modules 14 | 15 | 16 | A comprehensive list of changes in each version may be found in 17 | [the CHANGELOG](https://github.com/googleapis/nodejs-paginator/blob/main/CHANGELOG.md). 18 | 19 | * [Google Cloud Common Paginator Node.js Client API Reference][client-docs] 20 | 21 | * [github.com/googleapis/nodejs-paginator](https://github.com/googleapis/nodejs-paginator) 22 | 23 | Read more about the client libraries for Cloud APIs, including the older 24 | Google APIs Client Libraries, in [Client Libraries Explained][explained]. 25 | 26 | [explained]: https://cloud.google.com/apis/docs/client-libraries-explained 27 | 28 | **Table of contents:** 29 | 30 | 31 | * [Quickstart](#quickstart) 32 | 33 | * [Installing the client library](#installing-the-client-library) 34 | * [Using the client library](#using-the-client-library) 35 | * [Samples](#samples) 36 | * [Versioning](#versioning) 37 | * [Contributing](#contributing) 38 | * [License](#license) 39 | 40 | ## Quickstart 41 | 42 | ### Installing the client library 43 | 44 | ```bash 45 | npm install @google-cloud/paginator 46 | ``` 47 | 48 | 49 | ### Using the client library 50 | 51 | ```javascript 52 | const {paginator} = require('@google-cloud/paginator'); 53 | console.log(paginator); 54 | 55 | ``` 56 | 57 | 58 | 59 | ## Samples 60 | 61 | Samples are in the [`samples/`](https://github.com/googleapis/nodejs-paginator/tree/main/samples) directory. Each sample's `README.md` has instructions for running its sample. 62 | 63 | | Sample | Source Code | Try it | 64 | | --------------------------- | --------------------------------- | ------ | 65 | | Quickstart | [source code](https://github.com/googleapis/nodejs-paginator/blob/main/samples/quickstart.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-paginator&page=editor&open_in_editor=samples/quickstart.js,samples/README.md) | 66 | | Streamify | [source code](https://github.com/googleapis/nodejs-paginator/blob/main/samples/streamify.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-paginator&page=editor&open_in_editor=samples/streamify.js,samples/README.md) | 67 | 68 | 69 | 70 | The [Google Cloud Common Paginator Node.js Client API Reference][client-docs] documentation 71 | also contains samples. 72 | 73 | ## Supported Node.js Versions 74 | 75 | Our client libraries follow the [Node.js release schedule](https://github.com/nodejs/release#release-schedule). 76 | Libraries are compatible with all current _active_ and _maintenance_ versions of 77 | Node.js. 78 | If you are using an end-of-life version of Node.js, we recommend that you update 79 | as soon as possible to an actively supported LTS version. 80 | 81 | Google's client libraries support legacy versions of Node.js runtimes on a 82 | best-efforts basis with the following warnings: 83 | 84 | * Legacy versions are not tested in continuous integration. 85 | * Some security patches and features cannot be backported. 86 | * Dependencies cannot be kept up-to-date. 87 | 88 | Client libraries targeting some end-of-life versions of Node.js are available, and 89 | can be installed through npm [dist-tags](https://docs.npmjs.com/cli/dist-tag). 90 | The dist-tags follow the naming convention `legacy-(version)`. 91 | For example, `npm install @google-cloud/paginator@legacy-8` installs client libraries 92 | for versions compatible with Node.js 8. 93 | 94 | ## Versioning 95 | 96 | This library follows [Semantic Versioning](http://semver.org/). 97 | 98 | 99 | 100 | This library is considered to be **stable**. The code surface will not change in backwards-incompatible ways 101 | unless absolutely necessary (e.g. because of critical security issues) or with 102 | an extensive deprecation period. Issues and requests against **stable** libraries 103 | are addressed with the highest priority. 104 | 105 | 106 | 107 | 108 | 109 | 110 | More Information: [Google Cloud Platform Launch Stages][launch_stages] 111 | 112 | [launch_stages]: https://cloud.google.com/terms/launch-stages 113 | 114 | ## Contributing 115 | 116 | Contributions welcome! See the [Contributing Guide](https://github.com/googleapis/nodejs-paginator/blob/main/CONTRIBUTING.md). 117 | 118 | Please note that this `README.md`, the `samples/README.md`, 119 | and a variety of configuration files in this repository (including `.nycrc` and `tsconfig.json`) 120 | are generated from a central template. To edit one of these files, make an edit 121 | to its templates in 122 | [directory](https://github.com/googleapis/synthtool). 123 | 124 | ## License 125 | 126 | Apache Version 2.0 127 | 128 | See [LICENSE](https://github.com/googleapis/nodejs-paginator/blob/main/LICENSE) 129 | 130 | [client-docs]: https://cloud.google.com/nodejs/docs/reference/paginator/latest 131 | 132 | [shell_img]: https://gstatic.com/cloudssh/images/open-btn.png 133 | [projects]: https://console.cloud.google.com/project 134 | [billing]: https://support.google.com/cloud/answer/6293499#enable-billing 135 | 136 | [auth]: https://cloud.google.com/docs/authentication/external/set-up-adc-local 137 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright 2015 Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /*! 18 | * @module common/paginator 19 | */ 20 | 21 | import * as extend from 'extend'; 22 | import {TransformOptions} from 'stream'; 23 | import {ResourceStream} from './resource-stream'; 24 | 25 | export interface ParsedArguments extends TransformOptions { 26 | /** 27 | * Query object. This is most commonly an object, but to make the API more 28 | * simple, it can also be a string in some places. 29 | */ 30 | query?: ParsedArguments; 31 | 32 | /** 33 | * Callback function. 34 | */ 35 | callback?: Function; 36 | 37 | /** 38 | * Auto-pagination enabled. 39 | */ 40 | autoPaginate?: boolean; 41 | 42 | /** 43 | * Maximum API calls to make. 44 | */ 45 | maxApiCalls?: number; 46 | 47 | /** 48 | * Maximum results to return. 49 | */ 50 | maxResults?: number; 51 | 52 | pageSize?: number; 53 | 54 | streamOptions?: ParsedArguments; 55 | } 56 | 57 | /*! Developer Documentation 58 | * 59 | * paginator is used to auto-paginate `nextQuery` methods as well as 60 | * streamifying them. 61 | * 62 | * Before: 63 | * 64 | * search.query('done=true', function(err, results, nextQuery) { 65 | * search.query(nextQuery, function(err, results, nextQuery) {}); 66 | * }); 67 | * 68 | * After: 69 | * 70 | * search.query('done=true', function(err, results) {}); 71 | * 72 | * Methods to extend should be written to accept callbacks and return a 73 | * `nextQuery`. 74 | */ 75 | 76 | export class Paginator { 77 | /** 78 | * Cache the original method, then overwrite it on the Class's prototype. 79 | * 80 | * @param {function} Class - The parent class of the methods to extend. 81 | * @param {string|string[]} methodNames - Name(s) of the methods to extend. 82 | */ 83 | // tslint:disable-next-line:variable-name 84 | extend(Class: Function, methodNames: string | string[]) { 85 | if (typeof methodNames === 'string') { 86 | methodNames = [methodNames]; 87 | } 88 | methodNames.forEach(methodName => { 89 | const originalMethod = Class.prototype[methodName]; 90 | 91 | // map the original method to a private member 92 | Class.prototype[methodName + '_'] = originalMethod; 93 | 94 | // overwrite the original to auto-paginate 95 | /* eslint-disable @typescript-eslint/no-explicit-any */ 96 | Class.prototype[methodName] = function (...args: any[]) { 97 | const parsedArguments = paginator.parseArguments_(args); 98 | return paginator.run_(parsedArguments, originalMethod.bind(this)); 99 | }; 100 | }); 101 | } 102 | 103 | /** 104 | * Wraps paginated API calls in a readable object stream. 105 | * 106 | * This method simply calls the nextQuery recursively, emitting results to a 107 | * stream. The stream ends when `nextQuery` is null. 108 | * 109 | * `maxResults` will act as a cap for how many results are fetched and emitted 110 | * to the stream. 111 | * 112 | * @param {string} methodName - Name of the method to streamify. 113 | * @return {function} - Wrapped function. 114 | */ 115 | /* eslint-disable @typescript-eslint/no-explicit-any */ 116 | streamify(methodName: string) { 117 | return function ( 118 | // tslint:disable-next-line:no-any 119 | this: {[index: string]: Function}, 120 | /* eslint-disable @typescript-eslint/no-explicit-any */ 121 | ...args: any[] 122 | ): ResourceStream { 123 | const parsedArguments = paginator.parseArguments_(args); 124 | const originalMethod = this[methodName + '_'] || this[methodName]; 125 | return paginator.runAsStream_( 126 | parsedArguments, 127 | originalMethod.bind(this), 128 | ); 129 | }; 130 | } 131 | 132 | /** 133 | * Parse a pseudo-array `arguments` for a query and callback. 134 | * 135 | * @param {array} args - The original `arguments` pseduo-array that the original 136 | * method received. 137 | */ 138 | /* eslint-disable @typescript-eslint/no-explicit-any */ 139 | parseArguments_(args: any[]) { 140 | let query: string | ParsedArguments | undefined; 141 | let autoPaginate = true; 142 | let maxApiCalls = -1; 143 | let maxResults = -1; 144 | let callback: Function | undefined; 145 | 146 | const firstArgument = args[0]; 147 | const lastArgument = args[args.length - 1]; 148 | 149 | if (typeof firstArgument === 'function') { 150 | callback = firstArgument; 151 | } else { 152 | query = firstArgument; 153 | } 154 | 155 | if (typeof lastArgument === 'function') { 156 | callback = lastArgument; 157 | } 158 | 159 | if (typeof query === 'object') { 160 | query = extend<{}, ParsedArguments>(true, {}, query) as ParsedArguments; 161 | 162 | // Check if the user only asked for a certain amount of results. 163 | if (query.maxResults && typeof query.maxResults === 'number') { 164 | // `maxResults` is used API-wide. 165 | maxResults = query.maxResults; 166 | } else if (typeof query.pageSize === 'number') { 167 | // `pageSize` is Pub/Sub's `maxResults`. 168 | maxResults = query.pageSize!; 169 | } 170 | 171 | if (query.maxApiCalls && typeof query.maxApiCalls === 'number') { 172 | maxApiCalls = query.maxApiCalls; 173 | delete query.maxApiCalls; 174 | } 175 | 176 | // maxResults is the user specified limit. 177 | if (maxResults !== -1 || query.autoPaginate === false) { 178 | autoPaginate = false; 179 | } 180 | } 181 | 182 | const parsedArguments = { 183 | query: query || {}, 184 | autoPaginate, 185 | maxApiCalls, 186 | maxResults, 187 | callback, 188 | } as ParsedArguments; 189 | 190 | parsedArguments.streamOptions = extend<{}, ParsedArguments>( 191 | true, 192 | {}, 193 | parsedArguments.query as ParsedArguments, 194 | ); 195 | delete parsedArguments.streamOptions.autoPaginate; 196 | delete parsedArguments.streamOptions.maxResults; 197 | delete parsedArguments.streamOptions.pageSize; 198 | 199 | return parsedArguments; 200 | } 201 | 202 | /** 203 | * This simply checks to see if `autoPaginate` is set or not, if it's true 204 | * then we buffer all results, otherwise simply call the original method. 205 | * 206 | * @param {array} parsedArguments - Parsed arguments from the original method 207 | * call. 208 | * @param {object=|string=} parsedArguments.query - Query object. This is most 209 | * commonly an object, but to make the API more simple, it can also be a 210 | * string in some places. 211 | * @param {function=} parsedArguments.callback - Callback function. 212 | * @param {boolean} parsedArguments.autoPaginate - Auto-pagination enabled. 213 | * @param {boolean} parsedArguments.maxApiCalls - Maximum API calls to make. 214 | * @param {number} parsedArguments.maxResults - Maximum results to return. 215 | * @param {function} originalMethod - The cached method that accepts a callback 216 | * and returns `nextQuery` to receive more results. 217 | */ 218 | run_(parsedArguments: ParsedArguments, originalMethod: Function) { 219 | const query = parsedArguments.query; 220 | const callback = parsedArguments.callback!; 221 | if (!parsedArguments.autoPaginate) { 222 | return originalMethod(query, callback); 223 | } 224 | const results = new Array<{}>(); 225 | let otherArgs: unknown[] = []; 226 | const promise = new Promise((resolve, reject) => { 227 | const stream = paginator.runAsStream_(parsedArguments, originalMethod); 228 | stream 229 | .on('error', reject) 230 | .on('data', (data: {}) => results.push(data)) 231 | .on('end', () => { 232 | otherArgs = stream._otherArgs || []; 233 | resolve(results); 234 | }); 235 | }); 236 | if (!callback) { 237 | return promise.then(results => [results, query, ...otherArgs]); 238 | } 239 | promise.then( 240 | results => callback(null, results, query, ...otherArgs), 241 | (err: Error) => callback(err), 242 | ); 243 | } 244 | 245 | /** 246 | * This method simply calls the nextQuery recursively, emitting results to a 247 | * stream. The stream ends when `nextQuery` is null. 248 | * 249 | * `maxResults` will act as a cap for how many results are fetched and emitted 250 | * to the stream. 251 | * 252 | * @param {object=|string=} parsedArguments.query - Query object. This is most 253 | * commonly an object, but to make the API more simple, it can also be a 254 | * string in some places. 255 | * @param {function=} parsedArguments.callback - Callback function. 256 | * @param {boolean} parsedArguments.autoPaginate - Auto-pagination enabled. 257 | * @param {boolean} parsedArguments.maxApiCalls - Maximum API calls to make. 258 | * @param {number} parsedArguments.maxResults - Maximum results to return. 259 | * @param {function} originalMethod - The cached method that accepts a callback 260 | * and returns `nextQuery` to receive more results. 261 | * @return {stream} - Readable object stream. 262 | */ 263 | /* eslint-disable @typescript-eslint/no-explicit-any */ 264 | runAsStream_( 265 | parsedArguments: ParsedArguments, 266 | originalMethod: Function, 267 | ): ResourceStream { 268 | return new ResourceStream(parsedArguments, originalMethod); 269 | } 270 | } 271 | 272 | const paginator = new Paginator(); 273 | export {paginator}; 274 | 275 | export {ResourceStream}; 276 | -------------------------------------------------------------------------------- /test/resource-stream.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import {describe, it, beforeEach, afterEach} from 'mocha'; 17 | import * as sinon from 'sinon'; 18 | import {Transform} from 'stream'; 19 | 20 | import {ResourceStream} from '../src/resource-stream'; 21 | 22 | describe('ResourceStream', () => { 23 | const sandbox = sinon.createSandbox(); 24 | 25 | const config = { 26 | maxApiCalls: -1, 27 | maxResults: -1, 28 | query: {}, 29 | }; 30 | 31 | let requestSpy: sinon.SinonSpy; 32 | let stream: ResourceStream<{}>; 33 | 34 | beforeEach(() => { 35 | requestSpy = sandbox.spy(); 36 | stream = new ResourceStream(config, requestSpy); 37 | }); 38 | 39 | afterEach(() => { 40 | sandbox.restore(); 41 | }); 42 | 43 | describe('instantiation', () => { 44 | it('should pass the streamingOptions to the constructor', () => { 45 | const highWaterMark = 1; 46 | const options = { 47 | streamOptions: {highWaterMark}, 48 | }, 49 | stream = new ResourceStream(options, requestSpy); 50 | assert.strictEqual(stream.readableHighWaterMark, highWaterMark); 51 | }); 52 | 53 | it('should set ended to false', () => { 54 | assert.strictEqual(stream._ended, false); 55 | }); 56 | 57 | it('should set reading to false', () => { 58 | assert.strictEqual(stream._reading, false); 59 | }); 60 | 61 | it('should set requestsMade to 0', () => { 62 | assert.strictEqual(stream._requestsMade, 0); 63 | }); 64 | 65 | it('should localize the first query', () => { 66 | assert.strictEqual(stream._nextQuery, config.query); 67 | }); 68 | 69 | it('should localize the request function', () => { 70 | assert.strictEqual(stream._requestFn, requestSpy); 71 | }); 72 | 73 | describe('maxApiCalls', () => { 74 | it('should localize maxApiCalls', () => { 75 | const maxApiCalls = 100; 76 | stream = new ResourceStream({maxApiCalls}, requestSpy); 77 | assert.strictEqual(stream._maxApiCalls, maxApiCalls); 78 | }); 79 | 80 | it('should set it to Infinity if not specified', () => { 81 | assert.strictEqual(stream._maxApiCalls, Infinity); 82 | }); 83 | }); 84 | 85 | describe('resultsToSend', () => { 86 | it('should localize maxResults as resultsToSend', () => { 87 | const maxResults = 100; 88 | stream = new ResourceStream({maxResults}, requestSpy); 89 | assert.strictEqual(stream._resultsToSend, maxResults); 90 | }); 91 | 92 | it('should set it to Infinity if not specified', () => { 93 | assert.strictEqual(stream._resultsToSend, Infinity); 94 | }); 95 | }); 96 | }); 97 | 98 | describe('end', () => { 99 | it('should set ended to true', () => { 100 | stream.end(); 101 | assert.strictEqual(stream._ended, true); 102 | }); 103 | 104 | it('should call through to super.end', () => { 105 | const stub = sandbox.stub(Transform.prototype, 'end'); 106 | 107 | stream.end(); 108 | assert.strictEqual(stub.callCount, 1); 109 | }); 110 | }); 111 | 112 | describe('_read', () => { 113 | it('should set reading to true', () => { 114 | stream._read(); 115 | assert.strictEqual(stream._reading, true); 116 | }); 117 | 118 | it('should noop if already reading', () => { 119 | stream._read(); 120 | stream._read(); 121 | 122 | assert.strictEqual(requestSpy.callCount, 1); 123 | }); 124 | 125 | it('should pass in the query options', () => { 126 | stream._read(); 127 | 128 | const query = requestSpy.lastCall.args[0]; 129 | assert.strictEqual(query, config.query); 130 | }); 131 | 132 | it('should destroy the stream if an error occurs', () => { 133 | const fakeError = new Error('err'); 134 | const stub = sandbox.stub(stream, 'destroy').withArgs(fakeError); 135 | 136 | stream._read(); 137 | const callback = requestSpy.lastCall.args[1]; 138 | callback(fakeError); 139 | 140 | assert.strictEqual(stub.callCount, 1); 141 | }); 142 | 143 | it('should cache the next query', () => { 144 | const fakeQuery = {}; 145 | 146 | stream._read(); 147 | const callback = requestSpy.lastCall.args[1]; 148 | callback(null, [], fakeQuery); 149 | 150 | assert.strictEqual(stream._nextQuery, fakeQuery); 151 | }); 152 | 153 | it('should cache the rest of the callback arguments', () => { 154 | const fakeRes = {status: 'OK'}; 155 | const anotherArg = 10; 156 | 157 | stream._read(); 158 | const callback = requestSpy.lastCall.args[1]; 159 | callback(null, [], {}, fakeRes, anotherArg); 160 | 161 | assert.deepStrictEqual(stream._otherArgs, [fakeRes, anotherArg]); 162 | }); 163 | 164 | it('should adjust the results to send counter', () => { 165 | const maxResults = 100; 166 | const results = [{}, {}]; 167 | const expected = maxResults - results.length; 168 | 169 | stream = new ResourceStream({maxResults}, requestSpy); 170 | stream._read(); 171 | 172 | const callback = requestSpy.lastCall.args[1]; 173 | callback(null, results); 174 | 175 | assert.strictEqual(stream._resultsToSend, expected); 176 | }); 177 | 178 | it('should push in all the results', () => { 179 | // tslint:disable-next-line ban 180 | const results = Array(20).fill({}); 181 | const stub = sandbox.stub(stream, 'push'); 182 | 183 | stream._read(); 184 | const callback = requestSpy.lastCall.args[1]; 185 | callback(null, results, {}); 186 | 187 | assert.strictEqual(stub.callCount, results.length); 188 | 189 | results.forEach((result, i) => { 190 | const pushed = stub.getCall(i).args[0]; 191 | assert.strictEqual(pushed, result); 192 | }); 193 | }); 194 | 195 | it('should stop pushing results if the stream is ended', () => { 196 | // tslint:disable-next-line ban 197 | const results = Array(20).fill({}); 198 | 199 | stream.on('data', () => stream.end()); 200 | 201 | stream._read(); 202 | const callback = requestSpy.lastCall.args[1]; 203 | callback(null, results, {}); 204 | 205 | assert.strictEqual(requestSpy.callCount, 1); 206 | }); 207 | 208 | it('should end the stream if there is no next query', () => { 209 | const stub = sandbox.stub(stream, 'end'); 210 | 211 | stream._read(); 212 | const callback = requestSpy.lastCall.args[1]; 213 | callback(null, []); 214 | 215 | assert.strictEqual(stub.callCount, 1); 216 | }); 217 | 218 | it('should end the stream if max results is hit', () => { 219 | const maxResults = 10; 220 | // tslint:disable-next-line ban 221 | const results = Array(maxResults).fill({}); 222 | stream = new ResourceStream({maxResults}, requestSpy); 223 | const stub = sandbox.stub(stream, 'end'); 224 | 225 | stream._read(); 226 | const callback = requestSpy.lastCall.args[1]; 227 | callback(null, results, {}); 228 | 229 | assert.strictEqual(stub.callCount, 1); 230 | }); 231 | 232 | it('should end the stream if max api calls is hit', () => { 233 | const maxApiCalls = 1; 234 | stream = new ResourceStream({maxApiCalls}, requestSpy); 235 | const stub = sandbox.stub(stream, 'end'); 236 | 237 | stream._read(); 238 | const callback = requestSpy.lastCall.args[1]; 239 | callback(null, [], {}); 240 | 241 | assert.strictEqual(stub.callCount, 1); 242 | }); 243 | 244 | it('should stop reading if the buffer is full', () => { 245 | const clock = sandbox.useFakeTimers(); 246 | 247 | // tslint:disable-next-line ban 248 | const results = Array(stream.readableHighWaterMark).fill({}); 249 | stream._read(); 250 | const callback = requestSpy.lastCall.args[1]; 251 | callback(null, results, {}); 252 | 253 | const stub = sandbox.stub(stream, '_read'); 254 | clock.runAll(); 255 | 256 | assert.strictEqual(stub.callCount, 0); 257 | }); 258 | 259 | it('should stop reading if the stream ended', () => { 260 | const clock = sandbox.useFakeTimers(); 261 | 262 | stream.on('data', () => stream.end()); 263 | 264 | stream._read(); 265 | const callback = requestSpy.lastCall.args[1]; 266 | callback(null, [{}], {}); 267 | 268 | const stub = sandbox.stub(stream, '_read'); 269 | clock.runAll(); 270 | 271 | assert.strictEqual(stub.callCount, 0); 272 | }); 273 | 274 | it('should keep reading if not full/ended', () => { 275 | // sinon adds a timer to `nextTick` by default beginning in v19 276 | // manually specifying the timers like this replicates the behavior pre v19 277 | const clock = sandbox.useFakeTimers({ 278 | toFake: [ 279 | 'setTimeout', 280 | 'clearTimeout', 281 | 'setInterval', 282 | 'clearInterval', 283 | 'Date', 284 | 'setImmediate', 285 | 'clearImmediate', 286 | 'hrtime', 287 | 'performance', 288 | ], 289 | }); 290 | 291 | stream._read(); 292 | const callback = requestSpy.lastCall.args[1]; 293 | callback(null, [{}], {}); 294 | 295 | const stub = sandbox.stub(stream, '_read'); 296 | clock.runAll(); 297 | 298 | assert.strictEqual(stub.callCount, 1); 299 | }); 300 | 301 | it('should set reading to false inbetween reads', () => { 302 | stream._read(); 303 | const callback = requestSpy.lastCall.args[1]; 304 | callback(null, [{}], {}); 305 | 306 | assert.strictEqual(stream._reading, false); 307 | }); 308 | 309 | it('should destroy the stream if the request method throws', done => { 310 | const error = new Error('Error.'); 311 | stream._requestFn = () => { 312 | throw error; 313 | }; 314 | stream.on('error', err => { 315 | assert.strictEqual(err, error); 316 | done(); 317 | }); 318 | stream._read(); 319 | }); 320 | }); 321 | }); 322 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | [npm history][1] 4 | 5 | [1]: https://www.npmjs.com/package/nodejs-paginator?activeTab=versions 6 | 7 | ## [6.0.0](https://github.com/googleapis/nodejs-paginator/compare/v5.0.2...v6.0.0) (2025-03-04) 8 | 9 | 10 | ### ⚠ BREAKING CHANGES 11 | 12 | * migrate to node 18 ([#386](https://github.com/googleapis/nodejs-paginator/issues/386)) 13 | 14 | ### Miscellaneous Chores 15 | 16 | * Migrate to node 18 ([#386](https://github.com/googleapis/nodejs-paginator/issues/386)) ([1f4b49f](https://github.com/googleapis/nodejs-paginator/commit/1f4b49f1d0a0251a99b106adf6ce6a7e01f2fa27)) 17 | 18 | ## [5.0.2](https://github.com/googleapis/nodejs-paginator/compare/v5.0.1...v5.0.2) (2024-05-23) 19 | 20 | 21 | ### Bug Fixes 22 | 23 | * Query should be on the list of extra args ([#365](https://github.com/googleapis/nodejs-paginator/issues/365)) ([50e40d0](https://github.com/googleapis/nodejs-paginator/commit/50e40d064aed1bd0d5f93a51ad54112343086644)) 24 | 25 | ## [5.0.1](https://github.com/googleapis/nodejs-paginator/compare/v5.0.0...v5.0.1) (2024-05-22) 26 | 27 | 28 | ### Bug Fixes 29 | 30 | * Should pass extra callback arguments back to consumer ([#361](https://github.com/googleapis/nodejs-paginator/issues/361)) ([cc5c48b](https://github.com/googleapis/nodejs-paginator/commit/cc5c48b95b21e9c6a4e555ff98de267258657b6e)) 31 | 32 | ## [5.0.0](https://github.com/googleapis/nodejs-paginator/compare/v4.0.1...v5.0.0) (2023-08-09) 33 | 34 | 35 | ### ⚠ BREAKING CHANGES 36 | 37 | * update to Node 14 ([#346](https://github.com/googleapis/nodejs-paginator/issues/346)) 38 | 39 | ### Miscellaneous Chores 40 | 41 | * Update to Node 14 ([#346](https://github.com/googleapis/nodejs-paginator/issues/346)) ([262ad70](https://github.com/googleapis/nodejs-paginator/commit/262ad70d3cc5e1aa8a67ece54c04920b24ceea09)) 42 | 43 | ## [4.0.1](https://github.com/googleapis/nodejs-paginator/compare/v4.0.0...v4.0.1) (2022-09-09) 44 | 45 | 46 | ### Bug Fixes 47 | 48 | * Remove pip install statements ([#1546](https://github.com/googleapis/nodejs-paginator/issues/1546)) ([#329](https://github.com/googleapis/nodejs-paginator/issues/329)) ([697567b](https://github.com/googleapis/nodejs-paginator/commit/697567bdd86226b740304734b9562a2f2241a96f)) 49 | 50 | ## [4.0.0](https://github.com/googleapis/nodejs-paginator/compare/v3.0.7...v4.0.0) (2022-05-17) 51 | 52 | 53 | ### ⚠ BREAKING CHANGES 54 | 55 | * update library to use Node 12 (#325) 56 | 57 | ### Build System 58 | 59 | * update library to use Node 12 ([#325](https://github.com/googleapis/nodejs-paginator/issues/325)) ([02887ae](https://github.com/googleapis/nodejs-paginator/commit/02887ae2b370bff18cae7fe1d434ecdf663b5748)) 60 | 61 | ### [3.0.7](https://github.com/googleapis/nodejs-paginator/compare/v3.0.6...v3.0.7) (2022-02-14) 62 | 63 | 64 | ### Bug Fixes 65 | 66 | * update signature of end to comply with update node types definition ([#311](https://github.com/googleapis/nodejs-paginator/issues/311)) ([79e6fbd](https://github.com/googleapis/nodejs-paginator/commit/79e6fbdae5008d874613d2919a6cf723708fc919)) 67 | 68 | ### [3.0.6](https://www.github.com/googleapis/nodejs-paginator/compare/v3.0.5...v3.0.6) (2021-09-09) 69 | 70 | 71 | ### Bug Fixes 72 | 73 | * **build:** switch primary branch to main ([#287](https://www.github.com/googleapis/nodejs-paginator/issues/287)) ([1b796f3](https://www.github.com/googleapis/nodejs-paginator/commit/1b796f3377174354a62b7475d16f52213197f650)) 74 | 75 | ### [3.0.5](https://www.github.com/googleapis/nodejs-paginator/compare/v3.0.4...v3.0.5) (2020-09-02) 76 | 77 | 78 | ### Bug Fixes 79 | 80 | * add configs by running synthtool ([#241](https://www.github.com/googleapis/nodejs-paginator/issues/241)) ([643593a](https://www.github.com/googleapis/nodejs-paginator/commit/643593ae9ffb8febff69a7bdae19239f5bcb1266)) 81 | 82 | ### [3.0.4](https://www.github.com/googleapis/nodejs-paginator/compare/v3.0.3...v3.0.4) (2020-08-06) 83 | 84 | 85 | ### Bug Fixes 86 | 87 | * destroy ResourceStream with pre-flight error ([#236](https://www.github.com/googleapis/nodejs-paginator/issues/236)) ([d57beb4](https://www.github.com/googleapis/nodejs-paginator/commit/d57beb424d875a7bf502d458cc208f1bbe47a42a)) 88 | 89 | ### [3.0.3](https://www.github.com/googleapis/nodejs-paginator/compare/v3.0.2...v3.0.3) (2020-07-24) 90 | 91 | 92 | ### Bug Fixes 93 | 94 | * move gitattributes files to node templates ([#234](https://www.github.com/googleapis/nodejs-paginator/issues/234)) ([30e881c](https://www.github.com/googleapis/nodejs-paginator/commit/30e881ce7415749b93b6b7e4e71745ea3fb248b6)) 95 | 96 | ### [3.0.2](https://www.github.com/googleapis/nodejs-paginator/compare/v3.0.1...v3.0.2) (2020-07-06) 97 | 98 | 99 | ### Bug Fixes 100 | 101 | * update node issue template ([#221](https://www.github.com/googleapis/nodejs-paginator/issues/221)) ([088153c](https://www.github.com/googleapis/nodejs-paginator/commit/088153c4fca6d53e2e5ef4bb42365ce5493b913d)) 102 | 103 | ### [3.0.1](https://www.github.com/googleapis/nodejs-paginator/compare/v3.0.0...v3.0.1) (2020-05-20) 104 | 105 | 106 | ### Bug Fixes 107 | 108 | * apache license URL ([#468](https://www.github.com/googleapis/nodejs-paginator/issues/468)) ([#211](https://www.github.com/googleapis/nodejs-paginator/issues/211)) ([f343b7f](https://www.github.com/googleapis/nodejs-paginator/commit/f343b7f7e184fd1b453f20ac1463d17520aac7ad)) 109 | 110 | ## [3.0.0](https://www.github.com/googleapis/nodejs-paginator/compare/v2.0.3...v3.0.0) (2020-03-25) 111 | 112 | 113 | ### ⚠ BREAKING CHANGES 114 | 115 | * **dep:** upgrade gts 2.0.0 (#194) 116 | * **deps:** deprecated node 8 to 10; upgrade typescript 117 | 118 | ### Miscellaneous Chores 119 | 120 | * **dep:** upgrade gts 2.0.0 ([#194](https://www.github.com/googleapis/nodejs-paginator/issues/194)) ([4eaf9be](https://www.github.com/googleapis/nodejs-paginator/commit/4eaf9bed1fcfd0f10e877ff15c1d0e968e3356c8)) 121 | * **deps:** deprecated node 8 to 10; upgrade typescript ([f6434ab](https://www.github.com/googleapis/nodejs-paginator/commit/f6434ab9cacb6ab804c070f19c38b6072ca326b5)) 122 | 123 | ### [2.0.3](https://www.github.com/googleapis/nodejs-paginator/compare/v2.0.2...v2.0.3) (2019-12-05) 124 | 125 | 126 | ### Bug Fixes 127 | 128 | * **deps:** pin TypeScript below 3.7.0 ([e06e1b0](https://www.github.com/googleapis/nodejs-paginator/commit/e06e1b0a2e2bb1cf56fc806c1703b8b5e468b954)) 129 | 130 | ### [2.0.2](https://www.github.com/googleapis/nodejs-paginator/compare/v2.0.1...v2.0.2) (2019-11-13) 131 | 132 | 133 | ### Bug Fixes 134 | 135 | * **docs:** add jsdoc-region-tag plugin ([#155](https://www.github.com/googleapis/nodejs-paginator/issues/155)) ([b983799](https://www.github.com/googleapis/nodejs-paginator/commit/b98379905848fd179c6268aff3e1cfaf2bf76663)) 136 | 137 | ### [2.0.1](https://www.github.com/googleapis/nodejs-paginator/compare/v2.0.0...v2.0.1) (2019-08-25) 138 | 139 | 140 | ### Bug Fixes 141 | 142 | * **deps:** use the latest extend ([#141](https://www.github.com/googleapis/nodejs-paginator/issues/141)) ([61b383e](https://www.github.com/googleapis/nodejs-paginator/commit/61b383e)) 143 | 144 | ## [2.0.0](https://www.github.com/googleapis/nodejs-paginator/compare/v1.0.2...v2.0.0) (2019-07-12) 145 | 146 | 147 | ### ⚠ BREAKING CHANGES 148 | 149 | * rewrite streaming logic (#136) 150 | 151 | ### Code Refactoring 152 | 153 | * rewrite streaming logic ([#136](https://www.github.com/googleapis/nodejs-paginator/issues/136)) ([641d82d](https://www.github.com/googleapis/nodejs-paginator/commit/641d82d)) 154 | 155 | ### [1.0.2](https://www.github.com/googleapis/nodejs-paginator/compare/v1.0.1...v1.0.2) (2019-06-26) 156 | 157 | 158 | ### Bug Fixes 159 | 160 | * **docs:** link to reference docs section on googleapis.dev ([#132](https://www.github.com/googleapis/nodejs-paginator/issues/132)) ([be231be](https://www.github.com/googleapis/nodejs-paginator/commit/be231be)) 161 | 162 | ### [1.0.1](https://www.github.com/googleapis/nodejs-paginator/compare/v1.0.0...v1.0.1) (2019-06-14) 163 | 164 | 165 | ### Bug Fixes 166 | 167 | * **docs:** move to new client docs URL ([#129](https://www.github.com/googleapis/nodejs-paginator/issues/129)) ([689f483](https://www.github.com/googleapis/nodejs-paginator/commit/689f483)) 168 | 169 | ## [1.0.0](https://www.github.com/googleapis/nodejs-paginator/compare/v0.2.0...v1.0.0) (2019-05-03) 170 | 171 | 172 | ### Bug Fixes 173 | 174 | * **deps:** update dependency arrify to v2 ([#109](https://www.github.com/googleapis/nodejs-paginator/issues/109)) ([9f06c83](https://www.github.com/googleapis/nodejs-paginator/commit/9f06c83)) 175 | 176 | 177 | ### Build System 178 | 179 | * upgrade engines field to >=8.10.0 ([#115](https://www.github.com/googleapis/nodejs-paginator/issues/115)) ([0921076](https://www.github.com/googleapis/nodejs-paginator/commit/0921076)) 180 | 181 | 182 | ### BREAKING CHANGES 183 | 184 | * upgrade engines field to >=8.10.0 (#115) 185 | 186 | ## v0.2.0 187 | 188 | 03-08-2019 12:15 PST 189 | 190 | ### New Features 191 | - feat: handle promise based functions ([#91](https://github.com/googleapis/nodejs-paginator/pull/91)) 192 | - refactor(ts): create generic for object streams ([#101](https://github.com/googleapis/nodejs-paginator/pull/101)) 193 | 194 | ### Dependencies 195 | - chore(deps): update dependency through2 to v3 ([#53](https://github.com/googleapis/nodejs-paginator/pull/53)) 196 | - chore(deps): update dependency @types/is to v0.0.21 ([#55](https://github.com/googleapis/nodejs-paginator/pull/55)) 197 | - chore(deps): update dependency gts to ^0.9.0 ([#57](https://github.com/googleapis/nodejs-paginator/pull/57)) 198 | - fix: Pin @types/sinon to last compatible version ([#61](https://github.com/googleapis/nodejs-paginator/pull/61)) 199 | - refactor: trim a few dependencies ([#60](https://github.com/googleapis/nodejs-paginator/pull/60)) 200 | - chore(deps): update dependency @types/sinon to v5.0.7 ([#62](https://github.com/googleapis/nodejs-paginator/pull/62)) 201 | - chore(deps): update dependency @types/sinon to v7 ([#81](https://github.com/googleapis/nodejs-paginator/pull/81)) 202 | - chore(deps): update dependency mocha to v6 203 | 204 | ### Documentation 205 | - docs: add lint/fix example to contributing guide ([#85](https://github.com/googleapis/nodejs-paginator/pull/85)) 206 | - chore: move CONTRIBUTING.md to root ([#87](https://github.com/googleapis/nodejs-paginator/pull/87)) 207 | - docs: update links in contrib guide ([#94](https://github.com/googleapis/nodejs-paginator/pull/94)) 208 | - docs: update contributing path in README ([#88](https://github.com/googleapis/nodejs-paginator/pull/88)) 209 | 210 | ### Internal / Testing Changes 211 | - chore: include build in eslintignore ([#49](https://github.com/googleapis/nodejs-paginator/pull/49)) 212 | - chore: update CircleCI config ([#52](https://github.com/googleapis/nodejs-paginator/pull/52)) 213 | - chore: use latest npm on Windows ([#54](https://github.com/googleapis/nodejs-paginator/pull/54)) 214 | - chore: update eslintignore config ([#56](https://github.com/googleapis/nodejs-paginator/pull/56)) 215 | - chore: add synth.metadata 216 | - fix(build): fix system key decryption ([#64](https://github.com/googleapis/nodejs-paginator/pull/64)) 217 | - chore: update license file ([#68](https://github.com/googleapis/nodejs-paginator/pull/68)) 218 | - chore(build): update prettier config ([#69](https://github.com/googleapis/nodejs-paginator/pull/69)) 219 | - chore: nyc ignore build/test by default ([#71](https://github.com/googleapis/nodejs-paginator/pull/71)) 220 | - chore: always nyc report before calling codecov ([#72](https://github.com/googleapis/nodejs-paginator/pull/72)) 221 | - build: add Kokoro configs for autorelease ([#75](https://github.com/googleapis/nodejs-paginator/pull/75)) 222 | - fix(build): fix Kokoro release script ([#76](https://github.com/googleapis/nodejs-paginator/pull/76)) 223 | - chore: fix publish.sh permission +x ([#77](https://github.com/googleapis/nodejs-paginator/pull/77)) 224 | - chore: update nyc and eslint configs ([#79](https://github.com/googleapis/nodejs-paginator/pull/79)) 225 | - chore(build): inject yoshi automation key ([#80](https://github.com/googleapis/nodejs-paginator/pull/80)) 226 | - build: check broken links in generated docs ([#82](https://github.com/googleapis/nodejs-paginator/pull/82)) 227 | - build: ignore googleapis.com in doc link check ([#84](https://github.com/googleapis/nodejs-paginator/pull/84)) 228 | - build: test using @grpc/grpc-js in CI ([#89](https://github.com/googleapis/nodejs-paginator/pull/89)) 229 | - build: create docs test npm scripts ([#90](https://github.com/googleapis/nodejs-paginator/pull/90)) 230 | - build: use linkinator for docs test ([#93](https://github.com/googleapis/nodejs-paginator/pull/93)) 231 | - build: update release configuration 232 | - build: fix types for sinon ([#98](https://github.com/googleapis/nodejs-paginator/pull/98)) 233 | - build: use node10 to run samples-test, system-test etc ([#97](https://github.com/googleapis/nodejs-paginator/pull/97)) 234 | - build: Add docuploader credentials to node publish jobs ([#99](https://github.com/googleapis/nodejs-paginator/pull/99)) 235 | 236 | ## v0.1.2 237 | 238 | ### Bug fixes 239 | - fix: call limiter.makeRequest() instead of original method ([#43](https://github.com/googleapis/nodejs-paginator/pull/43)) 240 | 241 | ### Internal / Testing Changes 242 | - chore: update issue templates ([#42](https://github.com/googleapis/nodejs-paginator/pull/42)) 243 | - chore: remove old issue template ([#40](https://github.com/googleapis/nodejs-paginator/pull/40)) 244 | - build: run tests on node11 ([#39](https://github.com/googleapis/nodejs-paginator/pull/39)) 245 | - chores(build): run codecov on continuous builds ([#36](https://github.com/googleapis/nodejs-paginator/pull/36)) 246 | - chores(build): do not collect sponge.xml from windows builds ([#37](https://github.com/googleapis/nodejs-paginator/pull/37)) 247 | - chore: update new issue template ([#35](https://github.com/googleapis/nodejs-paginator/pull/35)) 248 | - chore(deps): update dependency sinon to v7 ([#31](https://github.com/googleapis/nodejs-paginator/pull/31)) 249 | - build: fix codecov uploading on Kokoro ([#32](https://github.com/googleapis/nodejs-paginator/pull/32)) 250 | - Update kokoro config ([#29](https://github.com/googleapis/nodejs-paginator/pull/29)) 251 | - Update CI config ([#27](https://github.com/googleapis/nodejs-paginator/pull/27)) 252 | - Don't publish sourcemaps ([#25](https://github.com/googleapis/nodejs-paginator/pull/25)) 253 | - build: prevent system/sample-test from leaking credentials 254 | - Update kokoro config ([#23](https://github.com/googleapis/nodejs-paginator/pull/23)) 255 | - test: remove appveyor config ([#22](https://github.com/googleapis/nodejs-paginator/pull/22)) 256 | - Update CI config ([#21](https://github.com/googleapis/nodejs-paginator/pull/21)) 257 | - Enable prefer-const in the eslint config ([#20](https://github.com/googleapis/nodejs-paginator/pull/20)) 258 | - Enable no-var in eslint ([#19](https://github.com/googleapis/nodejs-paginator/pull/19)) 259 | - Update CI config ([#18](https://github.com/googleapis/nodejs-paginator/pull/18)) 260 | 261 | ## v0.1.1 262 | 263 | ### Internal / Testing Changes 264 | - Add synth script and update CI config (#14) 265 | - chore(deps): update dependency nyc to v13 (#12) 266 | - chore: ignore package-lock.json (#11) 267 | - chore(deps): lock file maintenance (#10) 268 | - chore: update renovate config (#9) 269 | - remove that whitespace (#8) 270 | - chore(deps): lock file maintenance (#7) 271 | - chore(deps): update dependency typescript to v3 (#6) 272 | - chore: assert.deelEqual => assert.deepStrictEqual (#5) 273 | - chore: move mocha options to mocha.opts (#4) 274 | -------------------------------------------------------------------------------- /.kokoro/trampoline_v2.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2020 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | # trampoline_v2.sh 17 | # 18 | # If you want to make a change to this file, consider doing so at: 19 | # https://github.com/googlecloudplatform/docker-ci-helper 20 | # 21 | # This script is for running CI builds. For Kokoro builds, we 22 | # set this script to `build_file` field in the Kokoro configuration. 23 | 24 | # This script does 3 things. 25 | # 26 | # 1. Prepare the Docker image for the test 27 | # 2. Run the Docker with appropriate flags to run the test 28 | # 3. Upload the newly built Docker image 29 | # 30 | # in a way that is somewhat compatible with trampoline_v1. 31 | # 32 | # These environment variables are required: 33 | # TRAMPOLINE_IMAGE: The docker image to use. 34 | # TRAMPOLINE_DOCKERFILE: The location of the Dockerfile. 35 | # 36 | # You can optionally change these environment variables: 37 | # TRAMPOLINE_IMAGE_UPLOAD: 38 | # (true|false): Whether to upload the Docker image after the 39 | # successful builds. 40 | # TRAMPOLINE_BUILD_FILE: The script to run in the docker container. 41 | # TRAMPOLINE_WORKSPACE: The workspace path in the docker container. 42 | # Defaults to /workspace. 43 | # Potentially there are some repo specific envvars in .trampolinerc in 44 | # the project root. 45 | # 46 | # Here is an example for running this script. 47 | # TRAMPOLINE_IMAGE=gcr.io/cloud-devrel-kokoro-resources/node:10-user \ 48 | # TRAMPOLINE_BUILD_FILE=.kokoro/system-test.sh \ 49 | # .kokoro/trampoline_v2.sh 50 | 51 | set -euo pipefail 52 | 53 | TRAMPOLINE_VERSION="2.0.7" 54 | 55 | if command -v tput >/dev/null && [[ -n "${TERM:-}" ]]; then 56 | readonly IO_COLOR_RED="$(tput setaf 1)" 57 | readonly IO_COLOR_GREEN="$(tput setaf 2)" 58 | readonly IO_COLOR_YELLOW="$(tput setaf 3)" 59 | readonly IO_COLOR_RESET="$(tput sgr0)" 60 | else 61 | readonly IO_COLOR_RED="" 62 | readonly IO_COLOR_GREEN="" 63 | readonly IO_COLOR_YELLOW="" 64 | readonly IO_COLOR_RESET="" 65 | fi 66 | 67 | function function_exists { 68 | [ $(LC_ALL=C type -t $1)"" == "function" ] 69 | } 70 | 71 | # Logs a message using the given color. The first argument must be one 72 | # of the IO_COLOR_* variables defined above, such as 73 | # "${IO_COLOR_YELLOW}". The remaining arguments will be logged in the 74 | # given color. The log message will also have an RFC-3339 timestamp 75 | # prepended (in UTC). You can disable the color output by setting 76 | # TERM=vt100. 77 | function log_impl() { 78 | local color="$1" 79 | shift 80 | local timestamp="$(date -u "+%Y-%m-%dT%H:%M:%SZ")" 81 | echo "================================================================" 82 | echo "${color}${timestamp}:" "$@" "${IO_COLOR_RESET}" 83 | echo "================================================================" 84 | } 85 | 86 | # Logs the given message with normal coloring and a timestamp. 87 | function log() { 88 | log_impl "${IO_COLOR_RESET}" "$@" 89 | } 90 | 91 | # Logs the given message in green with a timestamp. 92 | function log_green() { 93 | log_impl "${IO_COLOR_GREEN}" "$@" 94 | } 95 | 96 | # Logs the given message in yellow with a timestamp. 97 | function log_yellow() { 98 | log_impl "${IO_COLOR_YELLOW}" "$@" 99 | } 100 | 101 | # Logs the given message in red with a timestamp. 102 | function log_red() { 103 | log_impl "${IO_COLOR_RED}" "$@" 104 | } 105 | 106 | readonly tmpdir=$(mktemp -d -t ci-XXXXXXXX) 107 | readonly tmphome="${tmpdir}/h" 108 | mkdir -p "${tmphome}" 109 | 110 | function cleanup() { 111 | rm -rf "${tmpdir}" 112 | } 113 | trap cleanup EXIT 114 | 115 | RUNNING_IN_CI="${RUNNING_IN_CI:-false}" 116 | 117 | # The workspace in the container, defaults to /workspace. 118 | TRAMPOLINE_WORKSPACE="${TRAMPOLINE_WORKSPACE:-/workspace}" 119 | 120 | pass_down_envvars=( 121 | # TRAMPOLINE_V2 variables. 122 | # Tells scripts whether they are running as part of CI or not. 123 | "RUNNING_IN_CI" 124 | # Indicates which CI system we're in. 125 | "TRAMPOLINE_CI" 126 | # Indicates the version of the script. 127 | "TRAMPOLINE_VERSION" 128 | # Contains path to build artifacts being executed. 129 | "KOKORO_BUILD_ARTIFACTS_SUBDIR" 130 | ) 131 | 132 | log_yellow "Building with Trampoline ${TRAMPOLINE_VERSION}" 133 | 134 | # Detect which CI systems we're in. If we're in any of the CI systems 135 | # we support, `RUNNING_IN_CI` will be true and `TRAMPOLINE_CI` will be 136 | # the name of the CI system. Both envvars will be passing down to the 137 | # container for telling which CI system we're in. 138 | if [[ -n "${KOKORO_BUILD_ID:-}" ]]; then 139 | # descriptive env var for indicating it's on CI. 140 | RUNNING_IN_CI="true" 141 | TRAMPOLINE_CI="kokoro" 142 | if [[ "${TRAMPOLINE_USE_LEGACY_SERVICE_ACCOUNT:-}" == "true" ]]; then 143 | if [[ ! -f "${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json" ]]; then 144 | log_red "${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json does not exist. Did you forget to mount cloud-devrel-kokoro-resources/trampoline? Aborting." 145 | exit 1 146 | fi 147 | # This service account will be activated later. 148 | TRAMPOLINE_SERVICE_ACCOUNT="${KOKORO_GFILE_DIR}/kokoro-trampoline.service-account.json" 149 | else 150 | if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then 151 | gcloud auth list 152 | fi 153 | log_yellow "Configuring Container Registry access" 154 | gcloud auth configure-docker --quiet 155 | fi 156 | pass_down_envvars+=( 157 | # KOKORO dynamic variables. 158 | "KOKORO_BUILD_NUMBER" 159 | "KOKORO_BUILD_ID" 160 | "KOKORO_JOB_NAME" 161 | "KOKORO_GIT_COMMIT" 162 | "KOKORO_GITHUB_COMMIT" 163 | "KOKORO_GITHUB_PULL_REQUEST_NUMBER" 164 | "KOKORO_GITHUB_PULL_REQUEST_COMMIT" 165 | # For flakybot 166 | "KOKORO_GITHUB_COMMIT_URL" 167 | "KOKORO_GITHUB_PULL_REQUEST_URL" 168 | ) 169 | elif [[ "${TRAVIS:-}" == "true" ]]; then 170 | RUNNING_IN_CI="true" 171 | TRAMPOLINE_CI="travis" 172 | pass_down_envvars+=( 173 | "TRAVIS_BRANCH" 174 | "TRAVIS_BUILD_ID" 175 | "TRAVIS_BUILD_NUMBER" 176 | "TRAVIS_BUILD_WEB_URL" 177 | "TRAVIS_COMMIT" 178 | "TRAVIS_COMMIT_MESSAGE" 179 | "TRAVIS_COMMIT_RANGE" 180 | "TRAVIS_JOB_NAME" 181 | "TRAVIS_JOB_NUMBER" 182 | "TRAVIS_JOB_WEB_URL" 183 | "TRAVIS_PULL_REQUEST" 184 | "TRAVIS_PULL_REQUEST_BRANCH" 185 | "TRAVIS_PULL_REQUEST_SHA" 186 | "TRAVIS_PULL_REQUEST_SLUG" 187 | "TRAVIS_REPO_SLUG" 188 | "TRAVIS_SECURE_ENV_VARS" 189 | "TRAVIS_TAG" 190 | ) 191 | elif [[ -n "${GITHUB_RUN_ID:-}" ]]; then 192 | RUNNING_IN_CI="true" 193 | TRAMPOLINE_CI="github-workflow" 194 | pass_down_envvars+=( 195 | "GITHUB_WORKFLOW" 196 | "GITHUB_RUN_ID" 197 | "GITHUB_RUN_NUMBER" 198 | "GITHUB_ACTION" 199 | "GITHUB_ACTIONS" 200 | "GITHUB_ACTOR" 201 | "GITHUB_REPOSITORY" 202 | "GITHUB_EVENT_NAME" 203 | "GITHUB_EVENT_PATH" 204 | "GITHUB_SHA" 205 | "GITHUB_REF" 206 | "GITHUB_HEAD_REF" 207 | "GITHUB_BASE_REF" 208 | ) 209 | elif [[ "${CIRCLECI:-}" == "true" ]]; then 210 | RUNNING_IN_CI="true" 211 | TRAMPOLINE_CI="circleci" 212 | pass_down_envvars+=( 213 | "CIRCLE_BRANCH" 214 | "CIRCLE_BUILD_NUM" 215 | "CIRCLE_BUILD_URL" 216 | "CIRCLE_COMPARE_URL" 217 | "CIRCLE_JOB" 218 | "CIRCLE_NODE_INDEX" 219 | "CIRCLE_NODE_TOTAL" 220 | "CIRCLE_PREVIOUS_BUILD_NUM" 221 | "CIRCLE_PROJECT_REPONAME" 222 | "CIRCLE_PROJECT_USERNAME" 223 | "CIRCLE_REPOSITORY_URL" 224 | "CIRCLE_SHA1" 225 | "CIRCLE_STAGE" 226 | "CIRCLE_USERNAME" 227 | "CIRCLE_WORKFLOW_ID" 228 | "CIRCLE_WORKFLOW_JOB_ID" 229 | "CIRCLE_WORKFLOW_UPSTREAM_JOB_IDS" 230 | "CIRCLE_WORKFLOW_WORKSPACE_ID" 231 | ) 232 | fi 233 | 234 | # Configure the service account for pulling the docker image. 235 | function repo_root() { 236 | local dir="$1" 237 | while [[ ! -d "${dir}/.git" ]]; do 238 | dir="$(dirname "$dir")" 239 | done 240 | echo "${dir}" 241 | } 242 | 243 | # Detect the project root. In CI builds, we assume the script is in 244 | # the git tree and traverse from there, otherwise, traverse from `pwd` 245 | # to find `.git` directory. 246 | if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then 247 | PROGRAM_PATH="$(realpath "$0")" 248 | PROGRAM_DIR="$(dirname "${PROGRAM_PATH}")" 249 | PROJECT_ROOT="$(repo_root "${PROGRAM_DIR}")" 250 | else 251 | PROJECT_ROOT="$(repo_root $(pwd))" 252 | fi 253 | 254 | log_yellow "Changing to the project root: ${PROJECT_ROOT}." 255 | cd "${PROJECT_ROOT}" 256 | 257 | # To support relative path for `TRAMPOLINE_SERVICE_ACCOUNT`, we need 258 | # to use this environment variable in `PROJECT_ROOT`. 259 | if [[ -n "${TRAMPOLINE_SERVICE_ACCOUNT:-}" ]]; then 260 | 261 | mkdir -p "${tmpdir}/gcloud" 262 | gcloud_config_dir="${tmpdir}/gcloud" 263 | 264 | log_yellow "Using isolated gcloud config: ${gcloud_config_dir}." 265 | export CLOUDSDK_CONFIG="${gcloud_config_dir}" 266 | 267 | log_yellow "Using ${TRAMPOLINE_SERVICE_ACCOUNT} for authentication." 268 | gcloud auth activate-service-account \ 269 | --key-file "${TRAMPOLINE_SERVICE_ACCOUNT}" 270 | log_yellow "Configuring Container Registry access" 271 | gcloud auth configure-docker --quiet 272 | fi 273 | 274 | required_envvars=( 275 | # The basic trampoline configurations. 276 | "TRAMPOLINE_IMAGE" 277 | "TRAMPOLINE_BUILD_FILE" 278 | ) 279 | 280 | if [[ -f "${PROJECT_ROOT}/.trampolinerc" ]]; then 281 | source "${PROJECT_ROOT}/.trampolinerc" 282 | fi 283 | 284 | log_yellow "Checking environment variables." 285 | for e in "${required_envvars[@]}" 286 | do 287 | if [[ -z "${!e:-}" ]]; then 288 | log "Missing ${e} env var. Aborting." 289 | exit 1 290 | fi 291 | done 292 | 293 | # We want to support legacy style TRAMPOLINE_BUILD_FILE used with V1 294 | # script: e.g. "github/repo-name/.kokoro/run_tests.sh" 295 | TRAMPOLINE_BUILD_FILE="${TRAMPOLINE_BUILD_FILE#github/*/}" 296 | log_yellow "Using TRAMPOLINE_BUILD_FILE: ${TRAMPOLINE_BUILD_FILE}" 297 | 298 | # ignore error on docker operations and test execution 299 | set +e 300 | 301 | log_yellow "Preparing Docker image." 302 | # We only download the docker image in CI builds. 303 | if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then 304 | # Download the docker image specified by `TRAMPOLINE_IMAGE` 305 | 306 | # We may want to add --max-concurrent-downloads flag. 307 | 308 | log_yellow "Start pulling the Docker image: ${TRAMPOLINE_IMAGE}." 309 | if docker pull "${TRAMPOLINE_IMAGE}"; then 310 | log_green "Finished pulling the Docker image: ${TRAMPOLINE_IMAGE}." 311 | has_image="true" 312 | else 313 | log_red "Failed pulling the Docker image: ${TRAMPOLINE_IMAGE}." 314 | has_image="false" 315 | fi 316 | else 317 | # For local run, check if we have the image. 318 | if docker images "${TRAMPOLINE_IMAGE}" | grep "${TRAMPOLINE_IMAGE%:*}"; then 319 | has_image="true" 320 | else 321 | has_image="false" 322 | fi 323 | fi 324 | 325 | 326 | # The default user for a Docker container has uid 0 (root). To avoid 327 | # creating root-owned files in the build directory we tell docker to 328 | # use the current user ID. 329 | user_uid="$(id -u)" 330 | user_gid="$(id -g)" 331 | user_name="$(id -un)" 332 | 333 | # To allow docker in docker, we add the user to the docker group in 334 | # the host os. 335 | docker_gid=$(cut -d: -f3 < <(getent group docker)) 336 | 337 | update_cache="false" 338 | if [[ "${TRAMPOLINE_DOCKERFILE:-none}" != "none" ]]; then 339 | # Build the Docker image from the source. 340 | context_dir=$(dirname "${TRAMPOLINE_DOCKERFILE}") 341 | docker_build_flags=( 342 | "-f" "${TRAMPOLINE_DOCKERFILE}" 343 | "-t" "${TRAMPOLINE_IMAGE}" 344 | "--build-arg" "UID=${user_uid}" 345 | "--build-arg" "USERNAME=${user_name}" 346 | ) 347 | if [[ "${has_image}" == "true" ]]; then 348 | docker_build_flags+=("--cache-from" "${TRAMPOLINE_IMAGE}") 349 | fi 350 | 351 | log_yellow "Start building the docker image." 352 | if [[ "${TRAMPOLINE_VERBOSE:-false}" == "true" ]]; then 353 | echo "docker build" "${docker_build_flags[@]}" "${context_dir}" 354 | fi 355 | 356 | # ON CI systems, we want to suppress docker build logs, only 357 | # output the logs when it fails. 358 | if [[ "${RUNNING_IN_CI:-}" == "true" ]]; then 359 | if docker build "${docker_build_flags[@]}" "${context_dir}" \ 360 | > "${tmpdir}/docker_build.log" 2>&1; then 361 | if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then 362 | cat "${tmpdir}/docker_build.log" 363 | fi 364 | 365 | log_green "Finished building the docker image." 366 | update_cache="true" 367 | else 368 | log_red "Failed to build the Docker image, aborting." 369 | log_yellow "Dumping the build logs:" 370 | cat "${tmpdir}/docker_build.log" 371 | exit 1 372 | fi 373 | else 374 | if docker build "${docker_build_flags[@]}" "${context_dir}"; then 375 | log_green "Finished building the docker image." 376 | update_cache="true" 377 | else 378 | log_red "Failed to build the Docker image, aborting." 379 | exit 1 380 | fi 381 | fi 382 | else 383 | if [[ "${has_image}" != "true" ]]; then 384 | log_red "We do not have ${TRAMPOLINE_IMAGE} locally, aborting." 385 | exit 1 386 | fi 387 | fi 388 | 389 | # We use an array for the flags so they are easier to document. 390 | docker_flags=( 391 | # Remove the container after it exists. 392 | "--rm" 393 | 394 | # Use the host network. 395 | "--network=host" 396 | 397 | # Run in priviledged mode. We are not using docker for sandboxing or 398 | # isolation, just for packaging our dev tools. 399 | "--privileged" 400 | 401 | # Run the docker script with the user id. Because the docker image gets to 402 | # write in ${PWD} you typically want this to be your user id. 403 | # To allow docker in docker, we need to use docker gid on the host. 404 | "--user" "${user_uid}:${docker_gid}" 405 | 406 | # Pass down the USER. 407 | "--env" "USER=${user_name}" 408 | 409 | # Mount the project directory inside the Docker container. 410 | "--volume" "${PROJECT_ROOT}:${TRAMPOLINE_WORKSPACE}" 411 | "--workdir" "${TRAMPOLINE_WORKSPACE}" 412 | "--env" "PROJECT_ROOT=${TRAMPOLINE_WORKSPACE}" 413 | 414 | # Mount the temporary home directory. 415 | "--volume" "${tmphome}:/h" 416 | "--env" "HOME=/h" 417 | 418 | # Allow docker in docker. 419 | "--volume" "/var/run/docker.sock:/var/run/docker.sock" 420 | 421 | # Mount the /tmp so that docker in docker can mount the files 422 | # there correctly. 423 | "--volume" "/tmp:/tmp" 424 | # Pass down the KOKORO_GFILE_DIR and KOKORO_KEYSTORE_DIR 425 | # TODO(tmatsuo): This part is not portable. 426 | "--env" "TRAMPOLINE_SECRET_DIR=/secrets" 427 | "--volume" "${KOKORO_GFILE_DIR:-/dev/shm}:/secrets/gfile" 428 | "--env" "KOKORO_GFILE_DIR=/secrets/gfile" 429 | "--volume" "${KOKORO_KEYSTORE_DIR:-/dev/shm}:/secrets/keystore" 430 | "--env" "KOKORO_KEYSTORE_DIR=/secrets/keystore" 431 | ) 432 | 433 | # Add an option for nicer output if the build gets a tty. 434 | if [[ -t 0 ]]; then 435 | docker_flags+=("-it") 436 | fi 437 | 438 | # Passing down env vars 439 | for e in "${pass_down_envvars[@]}" 440 | do 441 | if [[ -n "${!e:-}" ]]; then 442 | docker_flags+=("--env" "${e}=${!e}") 443 | fi 444 | done 445 | 446 | # If arguments are given, all arguments will become the commands run 447 | # in the container, otherwise run TRAMPOLINE_BUILD_FILE. 448 | if [[ $# -ge 1 ]]; then 449 | log_yellow "Running the given commands '" "${@:1}" "' in the container." 450 | readonly commands=("${@:1}") 451 | if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then 452 | echo docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" "${commands[@]}" 453 | fi 454 | docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" "${commands[@]}" 455 | else 456 | log_yellow "Running the tests in a Docker container." 457 | docker_flags+=("--entrypoint=${TRAMPOLINE_BUILD_FILE}") 458 | if [[ "${TRAMPOLINE_VERBOSE:-}" == "true" ]]; then 459 | echo docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" 460 | fi 461 | docker run "${docker_flags[@]}" "${TRAMPOLINE_IMAGE}" 462 | fi 463 | 464 | 465 | test_retval=$? 466 | 467 | if [[ ${test_retval} -eq 0 ]]; then 468 | log_green "Build finished with ${test_retval}" 469 | else 470 | log_red "Build finished with ${test_retval}" 471 | fi 472 | 473 | # Only upload it when the test passes. 474 | if [[ "${update_cache}" == "true" ]] && \ 475 | [[ $test_retval == 0 ]] && \ 476 | [[ "${TRAMPOLINE_IMAGE_UPLOAD:-false}" == "true" ]]; then 477 | log_yellow "Uploading the Docker image." 478 | if docker push "${TRAMPOLINE_IMAGE}"; then 479 | log_green "Finished uploading the Docker image." 480 | else 481 | log_red "Failed uploading the Docker image." 482 | fi 483 | # Call trampoline_after_upload_hook if it's defined. 484 | if function_exists trampoline_after_upload_hook; then 485 | trampoline_after_upload_hook 486 | fi 487 | 488 | fi 489 | 490 | exit "${test_retval}" 491 | -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import * as assert from 'assert'; 16 | import {describe, it, beforeEach, afterEach} from 'mocha'; 17 | import * as proxyquire from 'proxyquire'; 18 | import * as sinon from 'sinon'; 19 | import {PassThrough, Transform} from 'stream'; 20 | import * as uuid from 'uuid'; 21 | import * as P from '../src'; 22 | import {paginator, ParsedArguments} from '../src'; 23 | 24 | const util = { 25 | noop: () => { 26 | // do nothing 27 | }, 28 | }; 29 | 30 | class FakeResourceStream extends Transform { 31 | calledWith: IArguments; 32 | constructor() { 33 | super({objectMode: true}); 34 | /* eslint-disable-next-line prefer-rest-params */ 35 | this.calledWith = arguments; 36 | } 37 | } 38 | 39 | const p = proxyquire('../src', { 40 | './resource-stream': {ResourceStream: FakeResourceStream}, 41 | }) as typeof P; 42 | 43 | const sandbox = sinon.createSandbox(); 44 | 45 | // eslint-disable-next-line no-undef 46 | afterEach(() => { 47 | sandbox.restore(); 48 | }); 49 | 50 | /* eslint-disable @typescript-eslint/no-explicit-any */ 51 | function createFakeStream() { 52 | return new PassThrough({objectMode: true}) as P.ResourceStream; 53 | } 54 | 55 | describe('paginator', () => { 56 | const UUID = uuid.v1(); 57 | function FakeClass() { 58 | // do nothing 59 | } 60 | 61 | beforeEach(() => { 62 | FakeClass.prototype.methodToExtend = () => { 63 | return UUID; 64 | }; 65 | delete FakeClass.prototype.methodToExtend_; 66 | }); 67 | 68 | describe('extend', () => { 69 | it('should overwrite a method on a class', () => { 70 | const originalMethod = FakeClass.prototype.methodToExtend; 71 | paginator.extend(FakeClass, 'methodToExtend'); 72 | const overwrittenMethod = FakeClass.prototype.methodToExtend; 73 | assert.notStrictEqual(originalMethod, overwrittenMethod); 74 | }); 75 | 76 | it('should store the original method as a private member', () => { 77 | const originalMethod = FakeClass.prototype.methodToExtend; 78 | paginator.extend(FakeClass, 'methodToExtend'); 79 | assert.strictEqual(originalMethod, FakeClass.prototype.methodToExtend_); 80 | }); 81 | 82 | it('should accept an array or string method names', () => { 83 | const originalMethod = FakeClass.prototype.methodToExtend; 84 | const anotherMethod = FakeClass.prototype.anotherMethodToExtend; 85 | const methodsToExtend = ['methodToExtend', 'anotherMethodToExtend']; 86 | paginator.extend(FakeClass, methodsToExtend); 87 | assert.notStrictEqual(originalMethod, FakeClass.prototype.methodToExtend); 88 | assert.notStrictEqual( 89 | anotherMethod, 90 | FakeClass.prototype.anotherMethodToExtend, 91 | ); 92 | }); 93 | 94 | it('should parse the arguments', done => { 95 | sandbox.stub(paginator, 'parseArguments_').callsFake(args => { 96 | assert.deepStrictEqual([].slice.call(args), [1, 2, 3]); 97 | done(); 98 | return args as ParsedArguments; 99 | }); 100 | sandbox.stub(paginator, 'run_').callsFake(util.noop); 101 | paginator.extend(FakeClass, 'methodToExtend'); 102 | FakeClass.prototype.methodToExtend(1, 2, 3); 103 | }); 104 | 105 | it('should call router when the original method is called', done => { 106 | const expectedReturnValue = FakeClass.prototype.methodToExtend(); 107 | const parsedArguments = {a: 'b', c: 'd'} as ParsedArguments; 108 | 109 | sandbox.stub(paginator, 'parseArguments_').returns(parsedArguments); 110 | sandbox.stub(paginator, 'run_').callsFake((args, originalMethod) => { 111 | assert.strictEqual(args, parsedArguments); 112 | assert.strictEqual(originalMethod(), expectedReturnValue); 113 | done(); 114 | }); 115 | 116 | paginator.extend(FakeClass, 'methodToExtend'); 117 | FakeClass.prototype.methodToExtend(); 118 | }); 119 | 120 | it('should maintain `this` context', done => { 121 | FakeClass.prototype.methodToExtend = function () { 122 | return this.uuid; 123 | }; 124 | 125 | /* eslint-disable @typescript-eslint/no-explicit-any */ 126 | const cls = new (FakeClass as any)(); 127 | cls.uuid = uuid.v1(); 128 | 129 | sandbox.stub(paginator, 'run_').callsFake((_, originalMethod) => { 130 | assert.strictEqual(originalMethod(), cls.uuid); 131 | done(); 132 | }); 133 | 134 | paginator.extend(FakeClass, 'methodToExtend'); 135 | cls.methodToExtend(); 136 | }); 137 | 138 | it('should return what the router returns', () => { 139 | const uniqueValue = 234; 140 | sandbox.stub(paginator, 'run_').callsFake(() => { 141 | return uniqueValue; 142 | }); 143 | paginator.extend(FakeClass, 'methodToExtend'); 144 | assert.strictEqual(FakeClass.prototype.methodToExtend(), uniqueValue); 145 | }); 146 | }); 147 | 148 | describe('streamify', () => { 149 | beforeEach(() => { 150 | FakeClass.prototype.streamMethod = paginator.streamify('methodToExtend'); 151 | }); 152 | 153 | it('should return a function', () => { 154 | const fakeStreamMethod = FakeClass.prototype.streamMethod; 155 | assert.strictEqual(typeof fakeStreamMethod, 'function'); 156 | }); 157 | 158 | it('should parse the arguments', done => { 159 | const fakeArgs = [1, 2, 3]; 160 | 161 | sandbox.stub(paginator, 'parseArguments_').callsFake(args => { 162 | assert.deepStrictEqual(fakeArgs, [].slice.call(args)); 163 | done(); 164 | return args as ParsedArguments; 165 | }); 166 | sandbox.stub(paginator, 'runAsStream_').callsFake(createFakeStream); 167 | FakeClass.prototype.streamMethod(...fakeArgs); 168 | }); 169 | 170 | it('should run the method as a stream', done => { 171 | const parsedArguments = {a: 'b', c: 'd'} as ParsedArguments; 172 | sandbox.stub(paginator, 'parseArguments_').callsFake(() => { 173 | return parsedArguments; 174 | }); 175 | sandbox.stub(paginator, 'runAsStream_').callsFake((args, callback) => { 176 | assert.strictEqual(args, parsedArguments); 177 | assert.strictEqual(callback(), UUID); 178 | setImmediate(done); 179 | return createFakeStream(); 180 | }); 181 | 182 | FakeClass.prototype.streamMethod(); 183 | }); 184 | 185 | it('should apply the proper context', done => { 186 | const parsedArguments = {a: 'b', c: 'd'} as ParsedArguments; 187 | FakeClass.prototype.methodToExtend = function () { 188 | return this; 189 | }; 190 | sandbox.stub(paginator, 'parseArguments_').callsFake(() => { 191 | return parsedArguments; 192 | }); 193 | sandbox.stub(paginator, 'runAsStream_').callsFake((_, callback) => { 194 | assert.strictEqual(callback(), FakeClass.prototype); 195 | setImmediate(done); 196 | return createFakeStream(); 197 | }); 198 | FakeClass.prototype.streamMethod(); 199 | }); 200 | 201 | it('should check for a private member', done => { 202 | const parsedArguments = {a: 'b', c: 'd'} as ParsedArguments; 203 | const fakeValue = 123; 204 | 205 | FakeClass.prototype.methodToExtend_ = () => { 206 | return fakeValue; 207 | }; 208 | sandbox.stub(paginator, 'parseArguments_').callsFake(() => { 209 | return parsedArguments; 210 | }); 211 | sandbox.stub(paginator, 'runAsStream_').callsFake((_, callback) => { 212 | assert.strictEqual(callback(), fakeValue); 213 | setImmediate(done); 214 | return createFakeStream(); 215 | }); 216 | FakeClass.prototype.streamMethod(); 217 | }); 218 | 219 | it('should return a stream', () => { 220 | const fakeStream = createFakeStream(); 221 | sandbox.stub(paginator, 'parseArguments_').returns({}); 222 | sandbox.stub(paginator, 'runAsStream_').returns(fakeStream); 223 | const stream = FakeClass.prototype.streamMethod(); 224 | assert.strictEqual(fakeStream, stream); 225 | }); 226 | }); 227 | 228 | describe('parseArguments_', () => { 229 | it('should set defaults', () => { 230 | const parsedArguments = paginator.parseArguments_([]); 231 | 232 | assert.strictEqual(Object.keys(parsedArguments.query!).length, 0); 233 | assert.strictEqual(parsedArguments.autoPaginate, true); 234 | assert.strictEqual(parsedArguments.maxApiCalls, -1); 235 | assert.strictEqual(parsedArguments.maxResults, -1); 236 | assert.strictEqual(parsedArguments.callback, undefined); 237 | }); 238 | 239 | it('should detect a callback if first argument is a function', () => { 240 | const args = [util.noop]; 241 | const parsedArguments = paginator.parseArguments_(args); 242 | 243 | assert.strictEqual(parsedArguments.callback, args[0]); 244 | }); 245 | 246 | it('should use any other first argument as query', () => { 247 | const args = ['string']; 248 | const parsedArguments = paginator.parseArguments_(args); 249 | 250 | assert.strictEqual(parsedArguments.query, args[0]); 251 | }); 252 | 253 | it('should not make an undefined value the query', () => { 254 | const args = [undefined, util.noop]; 255 | const parsedArguments = paginator.parseArguments_(args); 256 | 257 | assert.deepStrictEqual(parsedArguments.query, {}); 258 | }); 259 | 260 | it('should detect a callback if last argument is a function', () => { 261 | const args = ['string', util.noop]; 262 | const parsedArguments = paginator.parseArguments_(args); 263 | 264 | assert.strictEqual(parsedArguments.callback, args[1]); 265 | }); 266 | 267 | it('should not assign a callback if a fn is not provided', () => { 268 | const args = ['string']; 269 | const parsedArguments = paginator.parseArguments_(args); 270 | 271 | assert.strictEqual(parsedArguments.callback, undefined); 272 | }); 273 | 274 | it('should set maxApiCalls from query.maxApiCalls', () => { 275 | const args = [{maxApiCalls: 10}]; 276 | const parsedArguments = paginator.parseArguments_(args); 277 | 278 | assert.strictEqual(parsedArguments.maxApiCalls, args[0].maxApiCalls); 279 | assert.strictEqual(parsedArguments.query!.maxApiCalls, undefined); 280 | }); 281 | 282 | it('should set maxResults from query.maxResults', () => { 283 | const args = [{maxResults: 10}]; 284 | const parsedArguments = paginator.parseArguments_(args); 285 | 286 | assert.strictEqual(parsedArguments.maxResults, args[0].maxResults); 287 | }); 288 | 289 | it('should set maxResults from query.pageSize', () => { 290 | const args = [{pageSize: 10}]; 291 | const parsedArguments = paginator.parseArguments_(args); 292 | 293 | assert.strictEqual(parsedArguments.maxResults, args[0].pageSize); 294 | }); 295 | 296 | it('should set autoPaginate: false if there is a maxResults', () => { 297 | const args = [{maxResults: 10}, util.noop]; 298 | const parsedArguments = paginator.parseArguments_(args); 299 | 300 | assert.strictEqual(parsedArguments.autoPaginate, false); 301 | }); 302 | 303 | it('should set autoPaginate: false query.autoPaginate', () => { 304 | const args = [{autoPaginate: false}, util.noop]; 305 | const parsedArguments = paginator.parseArguments_(args); 306 | 307 | assert.strictEqual(parsedArguments.autoPaginate, false); 308 | }); 309 | 310 | it('should parse streamOptions', () => { 311 | const args = [{maxResults: 10, highWaterMark: 8}]; 312 | const parsedArguments = paginator.parseArguments_(args); 313 | 314 | assert.strictEqual(parsedArguments.maxResults, 10); 315 | assert.deepStrictEqual(parsedArguments.streamOptions, { 316 | highWaterMark: 8, 317 | }); 318 | }); 319 | }); 320 | 321 | describe('run_', () => { 322 | describe('autoPaginate', () => { 323 | describe('originalmethod is callback based', () => { 324 | it('should call runAsStream_ when autoPaginate:true', done => { 325 | const parsedArguments = { 326 | autoPaginate: true, 327 | callback: util.noop, 328 | }; 329 | 330 | sandbox 331 | .stub(paginator, 'runAsStream_') 332 | .callsFake((args, originalMethod) => { 333 | assert.strictEqual(args, parsedArguments); 334 | originalMethod(); 335 | return createFakeStream(); 336 | }); 337 | 338 | paginator.run_(parsedArguments, done); 339 | }); 340 | 341 | it('should execute callback on error', done => { 342 | const error = new Error('Error.'); 343 | 344 | const parsedArguments = { 345 | autoPaginate: true, 346 | callback(err: Error) { 347 | assert.strictEqual(err, error); 348 | done(); 349 | }, 350 | }; 351 | 352 | sandbox.stub(paginator, 'runAsStream_').callsFake(() => { 353 | const stream = createFakeStream(); 354 | setImmediate(() => { 355 | stream.emit('error', error); 356 | }); 357 | return stream; 358 | }); 359 | 360 | paginator.run_(parsedArguments, util.noop); 361 | }); 362 | 363 | it('should return all results on end', done => { 364 | const results = [{a: 1}, {b: 2}, {c: 3}]; 365 | 366 | const parsedArguments = { 367 | autoPaginate: true, 368 | callback(err: Error, results_: {}) { 369 | assert.deepStrictEqual(results_, results); 370 | done(); 371 | }, 372 | }; 373 | 374 | sandbox.stub(paginator, 'runAsStream_').callsFake(() => { 375 | const stream = createFakeStream(); 376 | setImmediate(() => { 377 | results.forEach(result => stream.push(result)); 378 | stream.push(null); 379 | }); 380 | return stream; 381 | }); 382 | 383 | paginator.run_(parsedArguments, util.noop); 384 | }); 385 | 386 | it('should return all results and extra args', done => { 387 | const results = [{a: 1}, {b: 2}, {c: 3}]; 388 | const args: any[] = [{msg: 'OK'}, 10]; 389 | 390 | const parsedArguments = { 391 | autoPaginate: true, 392 | callback( 393 | err: Error, 394 | results_: {}, 395 | query: {}, 396 | fakeRes: {}, 397 | anotherArg: number, 398 | ) { 399 | assert.deepStrictEqual(results_, results); 400 | assert.deepStrictEqual(query, undefined); 401 | assert.deepStrictEqual(fakeRes, {msg: 'OK'}); 402 | assert.deepStrictEqual(anotherArg, 10); 403 | done(); 404 | }, 405 | }; 406 | 407 | sandbox.stub(paginator, 'runAsStream_').callsFake(() => { 408 | const stream = createFakeStream(); 409 | setImmediate(() => { 410 | results.forEach(result => stream.push(result)); 411 | stream.push(null); 412 | stream._otherArgs = args; 413 | }); 414 | return stream; 415 | }); 416 | 417 | paginator.run_(parsedArguments, util.noop); 418 | }); 419 | }); 420 | 421 | describe('original method is promise based', () => { 422 | const parsedArguments = { 423 | autoPaginate: true, 424 | }; 425 | it('should call runAsStream_ when autoPaginate:true', done => { 426 | sandbox 427 | .stub(paginator, 'runAsStream_') 428 | .callsFake((args, originalMethod) => { 429 | assert.strictEqual(args, parsedArguments); 430 | originalMethod(); 431 | return createFakeStream(); 432 | }); 433 | 434 | paginator.run_(parsedArguments, done); 435 | }); 436 | 437 | it('should reject a promise on error', () => { 438 | const error = new Error('Error.'); 439 | 440 | sandbox.stub(paginator, 'runAsStream_').callsFake(() => { 441 | const stream = createFakeStream(); 442 | setImmediate(() => { 443 | stream.emit('error', error); 444 | }); 445 | return stream; 446 | }); 447 | 448 | paginator 449 | .run_(parsedArguments, util.noop) 450 | .then(util.noop, (err: Error) => assert.strictEqual(err, error)); 451 | }); 452 | 453 | it('should resolve with all results on end', () => { 454 | const results = [{a: 1}, {b: 2}, {c: 3}]; 455 | 456 | sandbox.stub(paginator, 'runAsStream_').callsFake(() => { 457 | const stream = createFakeStream(); 458 | setImmediate(() => { 459 | results.forEach(result => stream.push(result)); 460 | stream.push(null); 461 | }); 462 | return stream; 463 | }); 464 | 465 | paginator 466 | .run_(parsedArguments, util.noop) 467 | .then(([results_]: [1]) => 468 | assert.deepStrictEqual(results_, results), 469 | ); 470 | }); 471 | 472 | it('should resolve with all results and extra args', done => { 473 | const results = [{a: 1}, {b: 2}, {c: 3}]; 474 | const args: any[] = [{msg: 'OK'}, 10]; 475 | 476 | sandbox.stub(paginator, 'runAsStream_').callsFake(() => { 477 | const stream = createFakeStream(); 478 | setImmediate(() => { 479 | results.forEach(result => stream.push(result)); 480 | stream.push(null); 481 | stream._otherArgs = args; 482 | }); 483 | return stream; 484 | }); 485 | 486 | paginator 487 | .run_(parsedArguments, util.noop) 488 | .then(([results_, query_, fakeRes, anotherArg]: unknown[]) => { 489 | assert.deepStrictEqual(results_, results); 490 | assert.deepStrictEqual(query_, undefined); 491 | assert.deepEqual(fakeRes, {msg: 'OK'}); 492 | assert.deepEqual(anotherArg, 10); 493 | done(); 494 | }); 495 | }); 496 | }); 497 | }); 498 | 499 | describe('manual pagination', () => { 500 | describe('originalmethod is callback based', () => { 501 | it('should recognize autoPaginate: false', done => { 502 | const parsedArguments = { 503 | autoPaginate: false, 504 | query: { 505 | a: 'b', 506 | c: 'd', 507 | }, 508 | callback: done, 509 | } as ParsedArguments; 510 | sandbox.stub(paginator, 'runAsStream_').callsFake(createFakeStream); 511 | paginator.run_(parsedArguments, (query: {}, callback: () => void) => { 512 | assert.deepStrictEqual(query, parsedArguments.query); 513 | callback(); 514 | }); 515 | }); 516 | }); 517 | 518 | describe('original method is promise based', () => { 519 | it('should recognize autoPaginate: false', () => { 520 | const parsedArguments = { 521 | autoPaginate: false, 522 | query: { 523 | a: 'b', 524 | c: 'd', 525 | }, 526 | } as ParsedArguments; 527 | sandbox.stub(paginator, 'runAsStream_').callsFake(createFakeStream); 528 | paginator.run_(parsedArguments, (query: {}) => { 529 | assert.deepStrictEqual(query, parsedArguments.query); 530 | }); 531 | }); 532 | }); 533 | }); 534 | 535 | describe('runAsStream_', () => { 536 | it('should create a resource stream', () => { 537 | const fakeArgs = {}; 538 | const fakeFn = sandbox.spy(); 539 | const stream = p.paginator.runAsStream_( 540 | fakeArgs, 541 | fakeFn, 542 | ) as unknown as FakeResourceStream; 543 | 544 | assert(stream instanceof FakeResourceStream); 545 | const [args, requestFn] = stream.calledWith; 546 | assert.strictEqual(args, fakeArgs); 547 | assert.strictEqual(requestFn, fakeFn); 548 | }); 549 | }); 550 | }); 551 | }); 552 | --------------------------------------------------------------------------------