From 475c7bbe57c5f74158143a03298f174373eecc84 Mon Sep 17 00:00:00 2001 From: kirillgroshkov Date: Tue, 26 Dec 2023 20:06:55 +0100 Subject: [PATCH 01/12] fix: move non-mandatory deps to peerDependencies fixes https://github.com/snowflakedb/snowflake-connector-nodejs/issues/449 --- README.md | 14 ++++++++++++-- lib/file_transfer_agent/s3_util.js | 2 +- package.json | 31 +++++++++++++++++++++++------- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 017cf99b9..2e9510729 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,19 @@ + +This is the fork of [snowflake-connector-nodejs](https://github.com/snowflakedb/snowflake-connector-nodejs) +with the following changes: + +- Fixes https://github.com/snowflakedb/snowflake-connector-nodejs/issues/449 by moving non-mandatory dependencies to peerDependencies. So, folks who don't need e.g AWS SDK don't have to "download the whole internet". + +Published as [@naturalcycles/snowflake-sdk](https://www.npmjs.com/package/@naturalcycles/snowflake-sdk). + +## Readme + ******************************************************************************** NodeJS Driver for Snowflake ********************************************************************************

master - npm + npm apache codecov

@@ -15,7 +25,7 @@ NodeJS Driver for Snowflake Install ====================================================================== -Run `npm i snowflake-sdk` in your existing NodeJs project. +Run `npm i @naturalcycles/snowflake-sdk` in your existing NodeJs project. Docs ====================================================================== diff --git a/lib/file_transfer_agent/s3_util.js b/lib/file_transfer_agent/s3_util.js index 522821125..527e51ddf 100644 --- a/lib/file_transfer_agent/s3_util.js +++ b/lib/file_transfer_agent/s3_util.js @@ -38,7 +38,6 @@ function S3Location(bucketName, s3path) { * @constructor */ function S3Util(s3, filestream) { - const AWS = typeof s3 !== 'undefined' ? s3 : require('@aws-sdk/client-s3'); const fs = typeof filestream !== 'undefined' ? filestream : require('fs'); // magic number, given from error message. @@ -73,6 +72,7 @@ function S3Util(s3, filestream) { useAccelerateEndpoint: useAccelerateEndpoint, }; + const AWS = typeof s3 !== 'undefined' ? s3 : require('@aws-sdk/client-s3'); return new AWS.S3(config); }; diff --git a/package.json b/package.json index 210c0bcc0..788099231 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,8 @@ { - "name": "snowflake-sdk", - "version": "1.9.2", + "name": "@naturalcycles/snowflake-sdk", + "version": "1.9.4", "description": "Node.js driver for Snowflake", "dependencies": { - "@aws-sdk/client-s3": "^3.388.0", - "@azure/storage-blob": "^12.11.0", - "@google-cloud/storage": "^6.9.3", "@techteamer/ocsp": "1.0.1", "agent-base": "^6.0.2", "asn1.js-rfc2560": "^5.0.0", @@ -46,14 +43,34 @@ "test-console": "^2.0.0" }, "peerDependencies": { - "asn1.js": "^5.4.1" + "asn1.js": "^5.4.1", + "@aws-sdk/client-s3": "^3.388.0", + "@azure/storage-blob": "^12.11.0", + "@google-cloud/storage": "^6.9.3" + }, + "peerDependenciesMeta": { + "asn1.js": { + "optional": true + }, + "@aws-sdk/client-s3": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@google-cloud/storage": { + "optional": true + } }, "overrides": { "semver": "^7.5.2" }, "repository": { "type": "git", - "url": "https://github.com/snowflakedb/snowflake-connector-nodejs" + "url": "https://github.com/NaturalCycles/snowflake-connector-nodejs" + }, + "publishConfig": { + "access": "public" }, "scripts": { "lint:check": "eslint", From 4c0a781c9bf18b5bed20cd83e3cbc9f2dafc2a85 Mon Sep 17 00:00:00 2001 From: kirillgroshkov Date: Wed, 27 Dec 2023 18:28:38 +0100 Subject: [PATCH 02/12] fix: remove more unused dependencies --- package.json | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 788099231..3a03a5962 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,9 @@ { "name": "@naturalcycles/snowflake-sdk", - "version": "1.9.4", + "version": "1.9.5", "description": "Node.js driver for Snowflake", "dependencies": { "@techteamer/ocsp": "1.0.1", - "agent-base": "^6.0.2", "asn1.js-rfc2560": "^5.0.0", "asn1.js-rfc5280": "^3.0.0", "axios": "^1.6.0", @@ -12,10 +11,7 @@ "bignumber.js": "^9.1.2", "binascii": "0.0.2", "bn.js": "^5.2.1", - "browser-request": "^0.3.3", - "debug": "^3.2.6", "expand-tilde": "^2.0.2", - "extend": "^3.0.2", "fast-xml-parser": "^4.2.5", "fastest-levenshtein": "^1.0.16", "generic-pool": "^3.8.2", @@ -36,6 +32,7 @@ "devDependencies": { "@aws-sdk/types": "^3.387.0", "async": "^3.2.3", + "browser-request": "^0.3.3", "eslint": "^8.41.0", "mocha": "^10.2.0", "mock-require": "^3.0.3", @@ -62,9 +59,6 @@ "optional": true } }, - "overrides": { - "semver": "^7.5.2" - }, "repository": { "type": "git", "url": "https://github.com/NaturalCycles/snowflake-connector-nodejs" From 3ef508ea1fb4a48877dc465d6cde0f8912fa89e7 Mon Sep 17 00:00:00 2001 From: kirillgroshkov Date: Fri, 31 May 2024 17:25:29 +0200 Subject: [PATCH 03/12] fix: types --- index.d.ts | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/index.d.ts b/index.d.ts index 7d494f730..f60e24196 100644 --- a/index.d.ts +++ b/index.d.ts @@ -6,7 +6,7 @@ * The snowflake-sdk module provides an instance to connect to the Snowflake server * @see [source] {@link https://docs.snowflake.com/en/developer-guide/node-js/nodejs-driver} */ -declare module 'snowflake-sdk' { +declare module '@naturalcycles/snowflake-sdk' { export const enum RowMode { ARRAY = 'array', OBJECT = 'object', @@ -491,7 +491,7 @@ declare module 'snowflake-sdk' { export interface StatementOption { sqlText: string; - complete: StatementCallback; + complete?: StatementCallback; /** * The requestId is for resubmitting requests. @@ -791,4 +791,4 @@ declare module 'snowflake-sdk' { * Creates a connection pool for Snowflake connections. */ export function createPool(options: ConnectionOptions, poolOptions?: PoolOptions): Pool; -} \ No newline at end of file +} diff --git a/package.json b/package.json index dd13f018e..c09009cb6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@naturalcycles/snowflake-sdk", - "version": "1.11.0", + "version": "1.11.1", "description": "Node.js driver for Snowflake", "dependencies": { "@techteamer/ocsp": "1.0.1", From 960f9e3dab140753f3272b2905a564a4b49f3039 Mon Sep 17 00:00:00 2001 From: kirillgroshkov Date: Thu, 14 Nov 2024 21:29:03 +0100 Subject: [PATCH 04/12] fix: index.d.ts module name --- index.d.ts | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/index.d.ts b/index.d.ts index 0d4e17bea..a7d8cb7cf 100644 --- a/index.d.ts +++ b/index.d.ts @@ -6,7 +6,7 @@ * The snowflake-sdk module provides an instance to connect to the Snowflake server * @see [source] {@link https://docs.snowflake.com/en/developer-guide/node-js/nodejs-driver} */ -declare module 'snowflake-sdk' { +declare module '@naturalcycles/snowflake-sdk' { enum ErrorCode { // 400001 diff --git a/package.json b/package.json index 44ba3ff6f..723a41e32 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@naturalcycles/snowflake-sdk", - "version": "1.15.0", + "version": "1.15.1", "description": "Node.js driver for Snowflake", "dependencies": { "@techteamer/ocsp": "1.0.1", From c9a1af69714d5c8b618d14b620d43266d9f999fe Mon Sep 17 00:00:00 2001 From: kirillgroshkov Date: Sun, 8 Jun 2025 09:45:01 +0200 Subject: [PATCH 05/12] fix: bump `@google-cloud/storage` peerDep to 7 to avoid peerDependency warning --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 723a41e32..50e2004bf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@naturalcycles/snowflake-sdk", - "version": "1.15.1", + "version": "1.15.2", "description": "Node.js driver for Snowflake", "dependencies": { "@techteamer/ocsp": "1.0.1", @@ -44,7 +44,7 @@ "asn1.js": "^5.4.1", "@aws-sdk/client-s3": "^3.388.0", "@azure/storage-blob": "^12.11.0", - "@google-cloud/storage": "^6.9.3" + "@google-cloud/storage": "^7" }, "peerDependenciesMeta": { "asn1.js": { From 9a98c991532e791e9c60c38eedc7fb0f7f4215a6 Mon Sep 17 00:00:00 2001 From: kirillgroshkov Date: Mon, 18 May 2026 22:48:38 +0200 Subject: [PATCH 06/12] chore: claude init --- .gitignore | 1 + CLAUDE.md | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 CLAUDE.md diff --git a/.gitignore b/.gitignore index 77d6b857b..8b9b54228 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ wss-*-agent.config wss-unified-agent.jar whitesource/ .nyc_output +/.claude/*.local.* diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..24574a866 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,73 @@ +## About this fork + +This is a fork of [`snowflake-connector-nodejs`](https://github.com/snowflakedb/snowflake-connector-nodejs) published as **`@naturalcycles/snowflake-sdk`**. +The motivating change vs. upstream is that the heavy cloud-storage SDKs +(`@aws-sdk/client-s3`, `@azure/storage-blob`, `@google-cloud/storage`, `asn1.js`) are declared as **optional `peerDependencies`** rather than hard dependencies — consumers who don't use a particular cloud's stage features don't have to install its SDK. +See `package.json` `peerDependencies` / `peerDependenciesMeta`. + +Two long-lived branches: +- `master` tracks upstream Snowflake releases. +- `next` is the active fork branch and the default target for PRs in this repo. + +The TypeScript declaration in `index.d.ts` uses `declare module '@naturalcycles/snowflake-sdk'` — if you ever need to re-sync with upstream, that line is the canonical place where the module name diverges. + +## Commands + +Tests are run with mocha and a 180s timeout. Unit tests do not require Snowflake credentials; integration and system tests do (see README §Test for the env var list). + +```bash +npm test # unit tests (same as test:unit) +npm run test:unit # unit tests +npm run test:integration # integration tests — needs SNOWFLAKE_TEST_* env vars +npm run test:system # system tests +npm run test:manual # interactive auth flows — needs RUN_MANUAL_TESTS_ONLY=true +npm run test:ci # unit + integration (CI matrix) +npm run test:ci:coverage # CI tests with nyc coverage + +# Run a single test file (or a specific describe via -g) +npm run test:single -- test/unit/snowflake_test.js +npm run test:single -- test/unit/snowflake_test.js -g 'pattern' +``` + +Some integration tests expect a local hang/proxy webserver: `python3 ci/container/hang_webserver.py 12345 &`. + +Lint (ESLint + `check-dts` for the `.d.ts`): + +```bash +npm run lint:check # eslint default (lib/) + check-dts index.d.ts +npm run lint:check:all # lint lib + samples + system_test + test +npm run lint:fix -- # autofix a file/dir +``` + +Pre-commit runs `snowflakedb/casec_precommit` (secret scanner) via `.pre-commit-config.yaml`. + +## Architecture + +Entry points: +- `index.js` → `lib/snowflake.js` (Node) — calls `core()` with `NodeHttpClient` and the Node logger. +- `lib/browser.js` is the browser entry, wired with `lib/http/browser.js` and `lib/logger/browser.js`. + +`lib/core.js` is a **factory**: it takes `{ httpClientClass, loggerClass, client, … }` and returns the public API (`createConnection`, `createPool`, `configure`, `STRING/NUMBER/…` type constants, error codes). The same `core()` is used for both node and browser builds — that's why platform differences live in pluggable classes, not in `core.js`. + +Layered structure under `lib/`: + +- **`connection/`** — `Connection`, `ConnectionConfig`, `ConnectionContext`, `Statement`, bind-uploading and result handling. A connection owns a `ConnectionContext`, which carries the `ConnectionConfig`, an `HttpClient`, and the `services`. +- **`services/`** — `sf.js` is the Snowflake session service (login, token refresh, query submission state machine). `large_result_set.js` handles chunked S3/GCS result downloads. +- **`authentication/`** — one module per auth type: `auth_default` (password), `auth_keypair` (JWT), `auth_oauth`, `auth_okta`, `auth_web` (browser SSO), `auth_idtoken`. `authentication.js` is the dispatcher. `secure_storage/json_credential_manager.js` is the default token cache. +- **`file_transfer_agent/`** — implements `PUT`/`GET` (stage upload/download) against S3 (`s3_util.js`), Azure (`azure_util.js`), GCS (`gcs_util.js`), or local (`local_util.js`). The cloud SDKs are loaded lazily so the optional peerDep model works — only the path that's actually used needs its SDK installed. `encrypt_util.js`, `file_compression_type.js`, and `file_util.js` are shared helpers. +- **`agent/`** — TLS / OCSP layer. `https_ocsp_agent.js` and `https_proxy_agent.js` extend Node's HTTPS agent to enforce OCSP revocation checking; `ocsp_response_cache.js` caches responses on disk. `cert_util.js` / `check.js` / `socket_util.js` support it. +- **`http/`** — pluggable HTTP clients: `base.js` (shared), `node.js` (axios + OCSP agent), `browser.js`. +- **`logger/`** — winston-based on Node, console-based in the browser. `easy_logging_starter.js` reads an external `client_config.json` for log-level/path overrides; `execution_timer.js` and `logging_utils.js` are shared. +- **`configuration/`** — `connection_configuration.js` loads connection params from a TOML file (`connections.toml`) when `createConnection()` is called without options. `client_configuration.js` handles the easy-logging JSON file. +- **`global_config.js`** — process-wide settings: `configure({ logLevel, ocspFailOpen/FailClosed/Insecure, customCredentialManager, ... })`. Mutates module state, so tests that touch it should restore it. +- **`secret_detector.js`** — scrubs secrets out of log messages before they're written. Anything that logs request/response bodies should go through this. +- **`queryContextCache.js`** — caches query-context entries returned by the server to optimize subsequent statements. +- **`errors.js`** — central `ErrorCode` enum + `Errors.createClientError(...)`. The numeric codes are the public API surface (mirrored in `index.d.ts`); don't renumber them. + +Cross-cutting note: the codebase still supports Node ≥ 6.0.0 (checked at startup in `lib/snowflake.js`). That's why `lib/` is plain CommonJS with no async/await in older files and a lot of callback-style code. Newer modules use modern syntax, but be mindful when adding language features in shared utilities. + +## Code style + +ESLint (`.eslintrc.js`) enforces: 2-space indent, single quotes, semicolons required, unix line endings, `eqeqeq` (with `null` exception), `camelCase`, `prefer-const`, `no-var`, `curly: all`, `no-console` (except `warn`/`error`; allowed everywhere in `samples/`). `space-before-function-paren` is `never` for named functions, `always` for anonymous and async-arrow. + +There is also a `webstorm-codestyle.xml` for JetBrains users. From f1ae799d13ed90dde4743e7335ff3327229ad55f Mon Sep 17 00:00:00 2001 From: kirillgroshkov Date: Mon, 18 May 2026 23:13:55 +0200 Subject: [PATCH 07/12] package.json: add cloud SDKs as devDependencies so build/tests work locally MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These packages are also declared as optional peerDependencies (the fork's slim-install pattern). Listing them in devDependencies as well means: - Local dev / CI: `npm install` pulls them in, so TypeScript compilation resolves their types and tests that exercise S3/Azure/GCP code paths can run. - Consumers installing @naturalcycles/snowflake-sdk: they remain optional peers — npm does not install them unless the consumer opts in. Also pin agent-base ^7.1.0 as a direct dependency to override the older 6.x copy that axios's transitive https-proxy-agent@5 hoists to the top level, which broke `lib/agent/https_proxy_agent.ts` (needs the v7 API). --- package.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/package.json b/package.json index a00a0c43b..bf1a75829 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ }, "dependencies": { "@techteamer/ocsp": "1.0.1", + "agent-base": "^7.1.0", "asn1.js-rfc2560": "^5.0.0", "asn1.js-rfc5280": "^3.0.0", "axios": "^1.15.1", @@ -28,12 +29,23 @@ "winston": "^3.1.0" }, "devDependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-sdk/client-s3": "~3.1045.0", + "@aws-sdk/client-sts": "~3.1045.0", + "@aws-sdk/credential-provider-node": "~3.972.37", + "@aws-sdk/ec2-metadata-service": "~3.1045.0", "@aws-sdk/types": "~3.973.8", + "@azure/identity": "^4.10.1", + "@azure/storage-blob": "12.26.x", "@napi-rs/cli": "^3.2.0", + "@smithy/node-http-handler": "^4.4.9", + "@smithy/protocol-http": "^5.3.8", + "@smithy/signature-v4": "^5.3.8", "@types/mocha": "^10.0.10", "@types/node": "^22.15.18", "@types/sinon": "^17.0.4", "async": "^3.2.3", + "google-auth-library": "^10.1.0", "husky": "^9.1.7", "oxlint": "^1.43.0", "lint-staged": "^16.0.0", From e3dfa5f4b91f5d01af23096c555cd304665652d2 Mon Sep 17 00:00:00 2001 From: kirillgroshkov Date: Mon, 18 May 2026 23:15:25 +0200 Subject: [PATCH 08/12] docs: rewrite CLAUDE.md for v2.x architecture post-upstream-sync Reflect the new reality after merging upstream v2.4.1: - Browser build is gone; TypeScript build produces dist/. - ESLint replaced with oxlint + prettier; husky/lint-staged pre-commit. - New auth: workload identity (AWS/Azure/GCP), PAT, OAuth authorization code/client credentials, auth coordinator, SPCS tokens. - New lib/minicore (NAPI Rust) with prebuilt binaries. - New lib/agent/crl_validator (CRL revocation). - New lib/telemetry, lib/disk_cache, lib/proxy_util. - Wiremock test harness for integration tests. - Node engine raised to >=18. Also document the fork-maintenance rules so the next upstream sync is less surprising: - The full peerDep set we maintain, and why each package is in devDependencies as well (build/test needs types). - The lazy-require discipline cloud-SDK callsites must follow so the optional peers can actually stay uninstalled at consumer install time. - The single divergence point (`declare module '@naturalcycles/snowflake-sdk'` in index.d.ts). --- CLAUDE.md | 111 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 69 insertions(+), 42 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 24574a866..97991c338 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,73 +1,100 @@ ## About this fork -This is a fork of [`snowflake-connector-nodejs`](https://github.com/snowflakedb/snowflake-connector-nodejs) published as **`@naturalcycles/snowflake-sdk`**. -The motivating change vs. upstream is that the heavy cloud-storage SDKs -(`@aws-sdk/client-s3`, `@azure/storage-blob`, `@google-cloud/storage`, `asn1.js`) are declared as **optional `peerDependencies`** rather than hard dependencies — consumers who don't use a particular cloud's stage features don't have to install its SDK. -See `package.json` `peerDependencies` / `peerDependenciesMeta`. +This is a fork of [`snowflake-connector-nodejs`](https://github.com/snowflakedb/snowflake-connector-nodejs) published as **`@naturalcycles/snowflake-sdk`**. + +The motivating change vs. upstream is that heavy cloud SDKs are declared as **optional `peerDependencies`** rather than hard `dependencies`. Consumers who don't use a particular cloud's stage or workload-identity features don't have to install its SDK. The current peer-dep set: + +- AWS: `@aws-sdk/client-s3`, `@aws-sdk/client-sts`, `@aws-sdk/credential-provider-node`, `@aws-sdk/ec2-metadata-service`, `@aws-crypto/sha256-js`, `@smithy/node-http-handler`, `@smithy/protocol-http`, `@smithy/signature-v4` +- Azure: `@azure/storage-blob`, `@azure/identity` +- GCP: `google-auth-library` +- Plus `asn1.js` (inherited from upstream's own optional peer) + +These same packages are **also listed in `devDependencies`** so local dev/CI installs them and the TypeScript build (`npm run prepack`) can resolve their types. They are only optional for downstream consumers. Two long-lived branches: -- `master` tracks upstream Snowflake releases. +- `master` tracks upstream Snowflake releases. Sync via the `upstream` remote (`https://github.com/snowflakedb/snowflake-connector-nodejs.git`). Merge `upstream/master` → local `master`, push, then merge `master` → `next`. - `next` is the active fork branch and the default target for PRs in this repo. -The TypeScript declaration in `index.d.ts` uses `declare module '@naturalcycles/snowflake-sdk'` — if you ever need to re-sync with upstream, that line is the canonical place where the module name diverges. +Re-sync risks to watch for when merging upstream into `next`: +- Upstream's `package.json` keeps adding hard cloud-SDK deps over time. Each sync must move new ones into `peerDependencies` (+ optional `peerDependenciesMeta` + duplicate in `devDependencies`). +- Upstream writes new `.ts` files (e.g. `lib/authentication/auth_workload_identity/*.ts`, `lib/telemetry/platform_detection.ts`) with **static `import`** from cloud SDKs. The static imports compile cleanly because we have the SDKs in `devDependencies`, but at runtime a consumer who hasn't installed them will throw on first `require` of those modules. Any new code path that touches a peer SDK must be reachable *only* through a callsite that itself fails gracefully when the SDK is missing (e.g. workload-identity attestation is only called when that auth mode is selected). +- The TS declaration in `index.d.ts` uses `declare module '@naturalcycles/snowflake-sdk'` — single divergence point for the module name. The file is copied verbatim into `dist/index.d.ts` by `ci/build_typescript.js`. ## Commands -Tests are run with mocha and a 180s timeout. Unit tests do not require Snowflake credentials; integration and system tests do (see README §Test for the env var list). +Build (TypeScript → `dist/`): ```bash -npm test # unit tests (same as test:unit) -npm run test:unit # unit tests -npm run test:integration # integration tests — needs SNOWFLAKE_TEST_* env vars -npm run test:system # system tests -npm run test:manual # interactive auth flows — needs RUN_MANUAL_TESTS_ONLY=true -npm run test:ci # unit + integration (CI matrix) -npm run test:ci:coverage # CI tests with nyc coverage - -# Run a single test file (or a specific describe via -g) +npm run prepack # tsc + copy index.d.ts + copy minicore binaries +npm run check-ts # prepack then `tsc --noEmit dist/index.d.ts` +``` + +Test (mocha, 180s timeout, runs both `.js` and `.ts` via `ts-node/register` from `.mocharc.js`): + +```bash +npm test # unit tests +npm run test:unit # same as `npm test` +npm run test:integration # integration — needs SNOWFLAKE_TEST_* env vars +npm run test:authentication # auth flow tests +npm run test:system # system tests +npm run test:manual # interactive auth — needs RUN_MANUAL_TESTS_ONLY=true +npm run test:ci # unit + integration combined +npm run test:ci:coverage # CI tests under nyc + +# Single test file (or filter via mocha's -g): npm run test:single -- test/unit/snowflake_test.js npm run test:single -- test/unit/snowflake_test.js -g 'pattern' ``` -Some integration tests expect a local hang/proxy webserver: `python3 ci/container/hang_webserver.py 12345 &`. +A subset of integration tests requires `python3 ci/container/hang_webserver.py 12345 &` to be running, plus an active wiremock server (`npm run serve-wiremock` on port 8081) for the `test/integration/wiremock/*` cases. -Lint (ESLint + `check-dts` for the `.d.ts`): +Lint / format (oxlint replaces ESLint; prettier handles formatting): ```bash -npm run lint:check # eslint default (lib/) + check-dts index.d.ts -npm run lint:check:all # lint lib + samples + system_test + test -npm run lint:fix -- # autofix a file/dir +npm run lint:check # oxlint . +npm run lint:fix # oxlint --fix . +npm run prettier:check # prettier --check . +npm run prettier:format # prettier -w . ``` -Pre-commit runs `snowflakedb/casec_precommit` (secret scanner) via `.pre-commit-config.yaml`. +`lint-staged` runs `prettier:format` on all staged files and `oxlint --max-warnings=0` on `.js`/`.ts` via the `husky` pre-commit hook (`.husky/pre-commit`). The separate `snowflakedb/casec_precommit` secret-scanner pre-commit (`.pre-commit-config.yaml`) is opt-in via `pre-commit install`. ## Architecture -Entry points: -- `index.js` → `lib/snowflake.js` (Node) — calls `core()` with `NodeHttpClient` and the Node logger. -- `lib/browser.js` is the browser entry, wired with `lib/http/browser.js` and `lib/logger/browser.js`. +**Entry points and build:** + +- `lib/snowflake.ts` is the source entry. It calls `core()` (`lib/core.js`) with `NodeHttpClient` and the Node logger. +- Root `index.js` re-exports `./lib/snowflake` (resolved by `ts-node` during dev/test). +- The published package's `main` is `./dist/index.js`, generated by `ci/build_typescript.js` (clears `dist/`, runs `tsc`, copies `index.d.ts` and the minicore binaries). The browser build was removed in v2.x. +- `tsconfig.json` has `allowJs: true` and `module: node16`, so `.ts` and `.js` files in `lib/` and `test/` are compiled together. `paths` maps `asn1.js` to a local type stub in `lib/types/asn1.js.d.ts` (asn1.js ships no types). + +**`lib/core.js`** is the **factory** that returns the public API (`createConnection`, `createPool`, `configure`, type constants, error codes). It takes pluggable `httpClientClass` and `loggerClass` — historically used to provide a browser variant, now only Node, but the indirection remains. -`lib/core.js` is a **factory**: it takes `{ httpClientClass, loggerClass, client, … }` and returns the public API (`createConnection`, `createPool`, `configure`, `STRING/NUMBER/…` type constants, error codes). The same `core()` is used for both node and browser builds — that's why platform differences live in pluggable classes, not in `core.js`. +**Layered structure under `lib/`:** -Layered structure under `lib/`: +- **`connection/`** — `Connection`, `ConnectionConfig`, `ConnectionContext`, `Statement`, bind uploading, result handling. A connection owns a `ConnectionContext` carrying config, HttpClient, and services. `normalize_connection_options.ts` and `types.ts` are the v2.x typed entry into option handling. +- **`services/`** — `sf.js` is the Snowflake session service (login, token refresh, query submission state machine). `large_result_set.js` downloads chunked S3/GCS result files. +- **`authentication/`** — one module per auth type. Legacy (`.js`): `auth_default` (password), `auth_idtoken`, `auth_keypair` (JWT), `auth_oauth`, `auth_oauth_authorization_code`, `auth_oauth_pat`, `auth_okta`, `auth_web` (browser SSO). v2.x additions (`.ts`): `auth_oauth_client_credentials`, `auth_coordinator` (orchestrates token caching across pooled connections), `spcs_token` (Snowpark Container Services), and the `auth_workload_identity/` subtree (AWS / Azure / GCP attestation). `authentication.js` is the dispatcher; `secure_storage/json_credential_manager.js` is the default disk-backed token cache. +- **`file_transfer_agent/`** — `PUT` / `GET` stage upload-download. `s3_util.js` (S3, via `@aws-sdk/client-s3` + `@smithy/node-http-handler` for proxy), `azure_util.js` (Azure via `@azure/storage-blob`), `gcs_util.js` (GCS via REST + `google-auth-library` for credentials), `local_util.js` (local stages). Cloud SDKs **must** be loaded lazily (the long-standing pattern is `typeof s3 !== 'undefined' ? s3 : require('@aws-sdk/client-s3')` inside the function that needs it). New code paths that touch a peer SDK must keep this discipline or the optional-peer install will break at first call. +- **`agent/`** — TLS layer. `https_ocsp_agent.js` + `ocsp_response_cache.js` enforce OCSP revocation. `https_proxy_agent.ts` (v2.x) handles outbound proxy and integrates with the new CRL validator. `crl_validator/` is a v2.x addition that fetches and verifies Certificate Revocation Lists, including RSASSA-PSS signature support (`rsassa_pss_parser.ts`). `socket_util.js` and `check.js` are shared helpers. +- **`http/`** — `base.js` (shared logic), `node.ts` (axios + OCSP/CRL agent), `node_untyped.js` (CJS shim), `axios_instance.ts` (single configured axios), `request_util.js` (retry, normalize response, GUID injection). +- **`logger/`** — winston-based (`logger.ts` + `logger/node.js`). `easy_logging_starter.js` reads an external `client_config.json` for log-level/path overrides. `execution_timer.js`, `logging_util.js` are shared. Browser logger was removed. +- **`configuration/`** — `connection_configuration.js` loads from a TOML file (`connections.toml`) when `createConnection()` is called without options. `client_configuration.js` handles the easy-logging JSON file. +- **`global_config.js`** — process-wide settings: `configure({ logLevel, ocspFailOpen/FailClosed/Insecure, customCredentialManager, ... })`. Mutates module state, so tests that touch it must restore it. `global_config_typed.ts` is the typed surface. +- **`secret_detector.js`** — scrubs secrets out of log messages. Anything that logs request/response bodies should go through this. +- **`queryContextCache.js`** — caches per-query context returned by the server to optimize subsequent statements. +- **`disk_cache.ts`** (v2.x) — generic disk-backed cache with permission checks; used by OAuth/PAT token caches. +- **`telemetry/`** (v2.x) — `inband_telemetry.ts` posts client telemetry to Snowflake. `platform_detection.ts`, `application_path.ts`, `libc_details.ts`, `os_details/` collect host info — `platform_detection` statically imports `@aws-sdk/client-sts` for ECS/EC2 attribution, so it's another path that needs the AWS SDK if invoked. +- **`minicore/`** (v2.x) — NAPI Rust module (`rust_minicore/`) shipping prebuilt `.node` binaries for darwin/linux/win × arm64/x64. Used for crypto/parser hot paths. `index.ts` is the JS entry; `minicore.ts` wraps the platform-specific binary. Prebuilds are checked into `lib/minicore/binaries/` and copied to `dist/lib/minicore/binaries/` by the build script. +- **`errors.js`** — central `ErrorCode` enum + `Errors.createClientError(...)`. The numeric codes are part of the public API surface (mirrored in `index.d.ts`); don't renumber them. `error_code.ts` is the typed re-export consumed by the `.d.ts`. +- **`proxy_util.js`** (v2.x) — proxy resolution from connection config / env (`HTTPS_PROXY`, `NO_PROXY`), with per-destination overrides. -- **`connection/`** — `Connection`, `ConnectionConfig`, `ConnectionContext`, `Statement`, bind-uploading and result handling. A connection owns a `ConnectionContext`, which carries the `ConnectionConfig`, an `HttpClient`, and the `services`. -- **`services/`** — `sf.js` is the Snowflake session service (login, token refresh, query submission state machine). `large_result_set.js` handles chunked S3/GCS result downloads. -- **`authentication/`** — one module per auth type: `auth_default` (password), `auth_keypair` (JWT), `auth_oauth`, `auth_okta`, `auth_web` (browser SSO), `auth_idtoken`. `authentication.js` is the dispatcher. `secure_storage/json_credential_manager.js` is the default token cache. -- **`file_transfer_agent/`** — implements `PUT`/`GET` (stage upload/download) against S3 (`s3_util.js`), Azure (`azure_util.js`), GCS (`gcs_util.js`), or local (`local_util.js`). The cloud SDKs are loaded lazily so the optional peerDep model works — only the path that's actually used needs its SDK installed. `encrypt_util.js`, `file_compression_type.js`, and `file_util.js` are shared helpers. -- **`agent/`** — TLS / OCSP layer. `https_ocsp_agent.js` and `https_proxy_agent.js` extend Node's HTTPS agent to enforce OCSP revocation checking; `ocsp_response_cache.js` caches responses on disk. `cert_util.js` / `check.js` / `socket_util.js` support it. -- **`http/`** — pluggable HTTP clients: `base.js` (shared), `node.js` (axios + OCSP agent), `browser.js`. -- **`logger/`** — winston-based on Node, console-based in the browser. `easy_logging_starter.js` reads an external `client_config.json` for log-level/path overrides; `execution_timer.js` and `logging_utils.js` are shared. -- **`configuration/`** — `connection_configuration.js` loads connection params from a TOML file (`connections.toml`) when `createConnection()` is called without options. `client_configuration.js` handles the easy-logging JSON file. -- **`global_config.js`** — process-wide settings: `configure({ logLevel, ocspFailOpen/FailClosed/Insecure, customCredentialManager, ... })`. Mutates module state, so tests that touch it should restore it. -- **`secret_detector.js`** — scrubs secrets out of log messages before they're written. Anything that logs request/response bodies should go through this. -- **`queryContextCache.js`** — caches query-context entries returned by the server to optimize subsequent statements. -- **`errors.js`** — central `ErrorCode` enum + `Errors.createClientError(...)`. The numeric codes are the public API surface (mirrored in `index.d.ts`); don't renumber them. +**Engine and language baseline:** Node ≥ 18 (`engines.node` in `package.json`; v1.x's Node-6 check is gone). New code goes in `.ts`; the `.ts`/`.js` boundary is fine to cross in either direction. Migration guidance is in the README's "TypeScript Migration" section. -Cross-cutting note: the codebase still supports Node ≥ 6.0.0 (checked at startup in `lib/snowflake.js`). That's why `lib/` is plain CommonJS with no async/await in older files and a lot of callback-style code. Newer modules use modern syntax, but be mindful when adding language features in shared utilities. +**Tests:** mocha config is in `.mocharc.js` (`ts-node/register`, `extension: ['js','ts']`, `recursive: true`, retries enabled). Wiremock-backed tests live in `test/integration/wiremock/` and consume mappings from `wiremock/mappings/`. Many of the 11 currently-failing unit tests in a fresh checkout are infrastructure-dependent (need `hang_webserver.py` / fixture files) — they fail the same way on a clean `master`, so a clean-merge baseline is `~1001 passing, ~11 failing` until that local setup is in place. ## Code style -ESLint (`.eslintrc.js`) enforces: 2-space indent, single quotes, semicolons required, unix line endings, `eqeqeq` (with `null` exception), `camelCase`, `prefer-const`, `no-var`, `curly: all`, `no-console` (except `warn`/`error`; allowed everywhere in `samples/`). `space-before-function-paren` is `never` for named functions, `always` for anonymous and async-arrow. +`oxlint` (`.oxlintrc.json`) is the linter; configuration is minimal — it's there as a fast gate, not as a strict style enforcer. **Formatting** is `prettier` (`.prettierrc.js`); run `npm run prettier:format` before committing. The pre-commit hook (`.husky/pre-commit` via `lint-staged`) runs both automatically on staged files. -There is also a `webstorm-codestyle.xml` for JetBrains users. +For JetBrains users, `webstorm-codestyle.xml` is still in the repo. From b03cd8886a0b3c0e866ed9e97a6da0c7c3da7654 Mon Sep 17 00:00:00 2001 From: kirillgroshkov Date: Mon, 18 May 2026 23:27:54 +0200 Subject: [PATCH 09/12] fix: lazy-load optional peer SDKs so the fork actually works without them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without these changes, the fork's slim-install promise is broken: simply calling `require('@naturalcycles/snowflake-sdk')` (or `require('./dist')`) crashes with MODULE_NOT_FOUND unless every cloud SDK is installed, because upstream v2.x added several eager-load chains: 1. lib/services/sf.js (loaded on every connection) eagerly requires lib/telemetry/platform_detection.ts, which static-imports @aws-sdk/client-sts. 2. lib/authentication/authentication.js eagerly requires auth_workload_identity.ts, which static-imports all three attestation_*.ts files, each of which static-imports its respective cloud SDK (@aws-sdk/*, @smithy/*, @aws-crypto/sha256-js, @azure/identity, google-auth-library). 3. lib/file_transfer_agent/remote_storage_util.js (loaded by every Statement) eagerly requires s3_util.js and azure_util.js, which had top-level `require('@smithy/node-http-handler')` and `require('@azure/storage-blob')`. Convert each static import to a `require()` inside the function that actually uses the SDK. TypeScript type information is preserved via `import type { ... }` so .d.ts output and IDE autocomplete still work. Verified by stashing every optional peer out of node_modules and calling `sdk.createConnection({...})` + force-loading the auth_workload_identity and platform_detection modules — all succeed. Test suite still reports the same 1001 passing / 11 failing baseline (failures are pre-existing infrastructure dependencies on hang_webserver.py and fixture files). --- .../auth_workload_identity/attestation_aws.ts | 46 ++++++++++++++++--- .../attestation_azure.ts | 6 ++- .../auth_workload_identity/attestation_gcp.ts | 10 +++- lib/file_transfer_agent/azure_util.js | 5 +- lib/file_transfer_agent/s3_util.js | 3 +- lib/telemetry/platform_detection.ts | 15 +++++- 6 files changed, 74 insertions(+), 11 deletions(-) diff --git a/lib/authentication/auth_workload_identity/attestation_aws.ts b/lib/authentication/auth_workload_identity/attestation_aws.ts index c69ab4fc2..4d787125a 100644 --- a/lib/authentication/auth_workload_identity/attestation_aws.ts +++ b/lib/authentication/auth_workload_identity/attestation_aws.ts @@ -1,12 +1,44 @@ -import { defaultProvider } from '@aws-sdk/credential-provider-node'; -import { STSClient, AssumeRoleCommand } from '@aws-sdk/client-sts'; -import { MetadataService } from '@aws-sdk/ec2-metadata-service'; -import { HttpRequest } from '@smithy/protocol-http'; -import { SignatureV4 } from '@smithy/signature-v4'; -import { Sha256 } from '@aws-crypto/sha256-js'; +import type { defaultProvider as _defaultProvider } from '@aws-sdk/credential-provider-node'; +import type { + STSClient as _STSClient, + AssumeRoleCommand as _AssumeRoleCommand, +} from '@aws-sdk/client-sts'; +import type { MetadataService as _MetadataService } from '@aws-sdk/ec2-metadata-service'; +import type { HttpRequest as _HttpRequest } from '@smithy/protocol-http'; +import type { SignatureV4 as _SignatureV4 } from '@smithy/signature-v4'; +import type { Sha256 as _Sha256 } from '@aws-crypto/sha256-js'; import Logger from '../../logger'; +// AWS / Smithy SDKs are optional peerDependencies in the @naturalcycles/snowflake-sdk fork. +// They're loaded lazily on first WIF use so consumers who don't use AWS workload-identity +// can install the package without pulling in the @aws-sdk/* tree. +type AwsSdk = { + defaultProvider: typeof _defaultProvider; + STSClient: typeof _STSClient; + AssumeRoleCommand: typeof _AssumeRoleCommand; + MetadataService: typeof _MetadataService; + HttpRequest: typeof _HttpRequest; + SignatureV4: typeof _SignatureV4; + Sha256: typeof _Sha256; +}; +let _sdk: AwsSdk | undefined; +function awsSdk(): AwsSdk { + if (!_sdk) { + _sdk = { + defaultProvider: require('@aws-sdk/credential-provider-node').defaultProvider, + STSClient: require('@aws-sdk/client-sts').STSClient, + AssumeRoleCommand: require('@aws-sdk/client-sts').AssumeRoleCommand, + MetadataService: require('@aws-sdk/ec2-metadata-service').MetadataService, + HttpRequest: require('@smithy/protocol-http').HttpRequest, + SignatureV4: require('@smithy/signature-v4').SignatureV4, + Sha256: require('@aws-crypto/sha256-js').Sha256, + }; + } + return _sdk; +} + export async function getAwsCredentials(region: string, impersonationPath: string[] = []) { + const { defaultProvider, STSClient, AssumeRoleCommand } = awsSdk(); Logger().debug('Getting AWS credentials from default provider'); let credentials = await defaultProvider()(); @@ -41,6 +73,7 @@ export async function getAwsRegion() { return process.env.AWS_REGION; // Lambda } else { Logger().debug('Getting AWS region from EC2 metadata service'); + const { MetadataService } = awsSdk(); return new MetadataService().request('/latest/meta-data/placement/region', {}); // EC2 } } @@ -51,6 +84,7 @@ export function getStsHostname(region: string) { } export async function getAwsAttestationToken(impersonationPath?: string[]) { + const { HttpRequest, SignatureV4, Sha256 } = awsSdk(); const region = await getAwsRegion(); const credentials = await getAwsCredentials(region, impersonationPath); diff --git a/lib/authentication/auth_workload_identity/attestation_azure.ts b/lib/authentication/auth_workload_identity/attestation_azure.ts index 9ca2809ba..ee83f3eb6 100644 --- a/lib/authentication/auth_workload_identity/attestation_azure.ts +++ b/lib/authentication/auth_workload_identity/attestation_azure.ts @@ -1,4 +1,4 @@ -import { DefaultAzureCredential } from '@azure/identity'; +import type { DefaultAzureCredential as _DefaultAzureCredential } from '@azure/identity'; import Logger from '../../logger'; export const DEFAULT_AZURE_ENTRA_ID_RESOURCE = 'api://fd3f753b-eed3-462c-b6a7-a4b5bb650aad'; @@ -9,6 +9,10 @@ export async function getAzureAttestationToken( entraIdResource?: string; } = {}, ) { + // @azure/identity is an optional peer in the @naturalcycles/snowflake-sdk fork; load lazily. + const { DefaultAzureCredential } = require('@azure/identity') as { + DefaultAzureCredential: typeof _DefaultAzureCredential; + }; const credential = new DefaultAzureCredential({ managedIdentityClientId: options.managedIdentityClientId, }); diff --git a/lib/authentication/auth_workload_identity/attestation_gcp.ts b/lib/authentication/auth_workload_identity/attestation_gcp.ts index 5479defeb..4517ba9eb 100644 --- a/lib/authentication/auth_workload_identity/attestation_gcp.ts +++ b/lib/authentication/auth_workload_identity/attestation_gcp.ts @@ -1,9 +1,17 @@ -import { GoogleAuth, Impersonated } from 'google-auth-library'; +import type { + GoogleAuth as _GoogleAuth, + Impersonated as _Impersonated, +} from 'google-auth-library'; import Logger from '../../logger'; export const SNOWFLAKE_AUDIENCE = 'snowflakecomputing.com'; export async function getGcpAttestationToken(impersonationPath?: string[]) { + // google-auth-library is an optional peer in the @naturalcycles/snowflake-sdk fork; load lazily. + const { GoogleAuth, Impersonated } = require('google-auth-library') as { + GoogleAuth: typeof _GoogleAuth; + Impersonated: typeof _Impersonated; + }; const auth = new GoogleAuth(); if (impersonationPath) { diff --git a/lib/file_transfer_agent/azure_util.js b/lib/file_transfer_agent/azure_util.js index 1f7b3f307..c334a0553 100644 --- a/lib/file_transfer_agent/azure_util.js +++ b/lib/file_transfer_agent/azure_util.js @@ -1,4 +1,6 @@ -const AZURE = require('@azure/storage-blob'); +// @azure/storage-blob is an optional peerDependency in the @naturalcycles/snowflake-sdk fork. +// It's loaded lazily inside createClient() so consumers that don't use Azure stages +// don't need it installed. const fs = require('fs'); const EncryptionMetadata = require('./encrypt_util').EncryptionMetadata; const FileHeader = require('../file_util').FileHeader; @@ -53,6 +55,7 @@ function AzureUtil(connectionConfig) { proxy = ProxyUtil.getAzureProxy(proxy); } ProxyUtil.hideEnvironmentProxy(); + const AZURE = require('@azure/storage-blob'); const blobServiceClient = new AZURE.BlobServiceClient(connectionString, null, { proxyOptions: proxy, }); diff --git a/lib/file_transfer_agent/s3_util.js b/lib/file_transfer_agent/s3_util.js index b6c45154e..ef74fb336 100644 --- a/lib/file_transfer_agent/s3_util.js +++ b/lib/file_transfer_agent/s3_util.js @@ -1,4 +1,4 @@ -const { NodeHttpHandler } = require('@smithy/node-http-handler'); +// @smithy/node-http-handler is an optional peerDependency; only required when a proxy is configured. const EncryptionMetadata = require('./encrypt_util').EncryptionMetadata; const FileHeader = require('../file_util').FileHeader; const expandTilde = require('expand-tilde'); @@ -84,6 +84,7 @@ function S3Util(connectionConfig, s3, filestream) { parsedUrl: new URL(connectionConfig.accessUrl), destination: endPoint || SNOWFLAKE_S3_DESTINATION, }); + const { NodeHttpHandler } = require('@smithy/node-http-handler'); config.requestHandler = new NodeHttpHandler({ httpAgent: proxyAgent, httpsAgent: proxyAgent, diff --git a/lib/telemetry/platform_detection.ts b/lib/telemetry/platform_detection.ts index 0cd66ae46..ba536417e 100644 --- a/lib/telemetry/platform_detection.ts +++ b/lib/telemetry/platform_detection.ts @@ -1,5 +1,11 @@ import http from 'node:http'; -import { STSClient, GetCallerIdentityCommand } from '@aws-sdk/client-sts'; +// Type-only import — the values are loaded lazily inside hasAwsIdentity() so that +// consumers who haven't installed the @aws-sdk/client-sts optional peer can +// still require this module (it's pulled in on every connection via sf.js). +import type { + STSClient as _STSClient, + GetCallerIdentityCommand as _GetCallerIdentityCommand, +} from '@aws-sdk/client-sts'; type Detector = (abortSignal: AbortSignal) => boolean | Promise; @@ -91,6 +97,13 @@ async function isEc2Instance(abortSignal: AbortSignal): Promise { } async function hasAwsIdentity(abortSignal: AbortSignal): Promise { + // Optional peer — only used by WIF detection; absence is treated as "not an AWS host". + let STSClient: typeof _STSClient, GetCallerIdentityCommand: typeof _GetCallerIdentityCommand; + try { + ({ STSClient, GetCallerIdentityCommand } = require('@aws-sdk/client-sts')); + } catch { + return false; + } const client = new STSClient({}); const response = await client.send(new GetCallerIdentityCommand({}), { abortSignal, From 11fee33799d04d13b7908a2cc4a2f98d418757cf Mon Sep 17 00:00:00 2001 From: kirillgroshkov Date: Mon, 18 May 2026 23:28:38 +0200 Subject: [PATCH 10/12] docs(CLAUDE.md): document the lazy-require pattern concretely Now that the audit is done, replace the forward-looking warning with the actual rule: name the files that follow the pattern, show the TypeScript snippet, and provide the verification one-liner for future upstream syncs. --- CLAUDE.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 97991c338..ad8f8eae0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,7 +17,20 @@ Two long-lived branches: Re-sync risks to watch for when merging upstream into `next`: - Upstream's `package.json` keeps adding hard cloud-SDK deps over time. Each sync must move new ones into `peerDependencies` (+ optional `peerDependenciesMeta` + duplicate in `devDependencies`). -- Upstream writes new `.ts` files (e.g. `lib/authentication/auth_workload_identity/*.ts`, `lib/telemetry/platform_detection.ts`) with **static `import`** from cloud SDKs. The static imports compile cleanly because we have the SDKs in `devDependencies`, but at runtime a consumer who hasn't installed them will throw on first `require` of those modules. Any new code path that touches a peer SDK must be reachable *only* through a callsite that itself fails gracefully when the SDK is missing (e.g. workload-identity attestation is only called when that auth mode is selected). +- Upstream writes new code with **static `import`/`require`** from cloud SDKs. The static imports compile cleanly because we have the SDKs in `devDependencies`, but at runtime a consumer who hasn't installed them will throw on first `require` of the module. **The lazy-require discipline is the load-bearing invariant of this fork.** Files that currently follow it (don't break them, and apply the same pattern to any new peer-SDK callsite): + - `lib/telemetry/platform_detection.ts` — uses `import type` only; requires `@aws-sdk/client-sts` inside `hasAwsIdentity()`. **Critical** because `lib/services/sf.js` requires this module on every connection. + - `lib/authentication/auth_workload_identity/attestation_aws.ts` — `import type` only; the AWS / `@smithy/*` / `@aws-crypto/sha256-js` bundle is loaded by an internal `awsSdk()` helper on first use. **Critical** because `lib/authentication/authentication.js` requires `auth_workload_identity` eagerly. + - `lib/authentication/auth_workload_identity/attestation_azure.ts` and `attestation_gcp.ts` — same pattern with `@azure/identity` and `google-auth-library`. + - `lib/file_transfer_agent/s3_util.js` — `@aws-sdk/client-s3` is required inside the `S3Util` constructor, `@smithy/node-http-handler` is required inside the proxy `if` block. **Critical** because `remote_storage_util.js` requires this module eagerly when `Statement` loads. + - `lib/file_transfer_agent/azure_util.js` — `@azure/storage-blob` is required inside `createClient()`. **Critical** for the same reason. +- The pattern in TypeScript files: + ```ts + import type { STSClient as _STSClient } from '@aws-sdk/client-sts'; + // ...inside the function that actually uses it: + const { STSClient } = require('@aws-sdk/client-sts') as { STSClient: typeof _STSClient }; + ``` + Cast to `typeof ` so callers keep their type info and the response type isn't widened. +- Verify after every upstream sync: stash `node_modules/@aws-sdk`, `@aws-crypto`, `@smithy`, `@azure`, `google-auth-library` aside, then `node -e "require('./dist'); const c = require('./dist').createConnection({account:'x',username:'u',password:'p'}); require('./dist/lib/authentication/auth_workload_identity/auth_workload_identity'); require('./dist/lib/telemetry/platform_detection');"` — must complete without `MODULE_NOT_FOUND`. - The TS declaration in `index.d.ts` uses `declare module '@naturalcycles/snowflake-sdk'` — single divergence point for the module name. The file is copied verbatim into `dist/index.d.ts` by `ci/build_typescript.js`. ## Commands From f46e457bc5b31018fb5bdaee1b61081053ad9a0d Mon Sep 17 00:00:00 2001 From: kirillgroshkov Date: Mon, 18 May 2026 23:36:26 +0200 Subject: [PATCH 11/12] package.json: drop explicit agent-base dependency When this was added (commit f1ae799), the cloud-SDK devDependencies hadn't been added yet, so npm's top-level resolution of `agent-base` landed on 6.0.2 (transitive via axios -> https-proxy-agent@5), and `tsc` couldn't find the v7-only `AgentConnectOpts` type that lib/agent/https_proxy_agent.ts imports. After adding the cloud SDKs to devDependencies, @azure/identity -> @azure/core-rest-pipeline -> @typespec/ts-http-runtime -> http-proxy-agent@7 pulls agent-base@7.1.x to the top level on its own, so the explicit dep is redundant for the build. At runtime our direct dep on https-proxy-agent@7 ensures the v7 API is used regardless. Removing brings us closer to upstream's package.json (less divergence to maintain on future syncs). Verified: clean install yields 712 packages instead of 713; tsc passes; tests still report 1001/11. --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index bf1a75829..480e7979a 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,6 @@ }, "dependencies": { "@techteamer/ocsp": "1.0.1", - "agent-base": "^7.1.0", "asn1.js-rfc2560": "^5.0.0", "asn1.js-rfc5280": "^3.0.0", "axios": "^1.15.1", From 35d39747c849b79a9783e5b04acce008370961ff Mon Sep 17 00:00:00 2001 From: kirillgroshkov Date: Mon, 18 May 2026 23:53:29 +0200 Subject: [PATCH 12/12] .npmignore: exclude .map files from the published pack Local TypeScript build still emits sourcemaps (useful for in-tree debugging via ts-node and for stepping through dist/ during local investigation), but consumers don't benefit from them. Excluding removes 108 files (~600 KB unpacked, ~100 KB packed) from the published @naturalcycles/snowflake-sdk tarball. dist/test/ was already correctly excluded by the existing `dist/test/**/*` rule; verified via `npm pack --dry-run`. --- .npmignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.npmignore b/.npmignore index 69ac7b749..309604d8e 100644 --- a/.npmignore +++ b/.npmignore @@ -1,6 +1,7 @@ * !dist/**/* dist/test/**/* +dist/**/*.map !LICENSE !README.md !SECURITY.md