diff --git a/.eslintignore b/.eslintignore index 8025ae1..4d6880d 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,2 @@ dist examples -PUBLIC-API.baseline.d.ts diff --git a/DEPENDENCY-AUDIT.md b/DEPENDENCY-AUDIT.md deleted file mode 100644 index 5a55f19..0000000 --- a/DEPENDENCY-AUDIT.md +++ /dev/null @@ -1,229 +0,0 @@ -# Dependency Audit & EOL Remediation Plan - -**Package:** `@jupiterone/jupiterone-client-nodejs` v2.1.2 -**Date:** 2026-10-20 -**Jira:** [TD-9488](https://jupiterone.atlassian.net/browse/TD-9488) -**Audit tool:** `yarn audit` (586 deps) - ---- - -## 0. TD-9488 Scope - -The ticket names two EOL items explicitly: - -1. **`graphql@14.7.0`** — EOL. → Phase 2 (Apollo + GraphQL). -2. **`bunyan → mv → rimraf@2.4.5 → glob@6.0.4 → inflight@1.0.6` chain** — `inflight` - is deprecated (memory leak, unmaintained). → Phase 1b below. - -**Core:** these two items + the transitive critical/high resolutions (Phase 0). -**Follow-up:** full tooling/CLI fleet upgrade (Phases 1a, 3) — ship as a later release so -the core isn't blocked by broad churn. - -### Phase 1b — Kill the `inflight` chain -Source: `bunyan-category@0.4.0` → `bunyan@1.8.15`. The `inflight` leak enters via -bunyan's **`optionalDependencies`** (`mv`, `moment`, `dtrace-provider`, -`safe-json-stringify`) — rotating-file logging we don't use. Options, cheapest first: - -- **A (fast):** drop bunyan's optional deps — install `--ignore-optional` in CI, or add - `resolutions` to force `mv`/`rimraf`/`glob` out of the tree. Keeps the logger API. Verify - `src/index.ts:11` (`import Logger, { createLogger } from 'bunyan-category'`) still works. -- **B (clean):** replace `bunyan-category` logger with a maintained logger (e.g. `pino`). - Kills the whole EOL bunyan tree; requires rewriting logger usage in `src/index.ts`. - -Recommend **A** for the core release; file **B** as follow-up. - ---- - -## 1. Vulnerability Summary - -| Severity | Count | -|----------|-------| -| Critical | 13 | -| High | 186 | -| Moderate | 99 | -| Low | 24 | -| **Total** | **322** | - -33 distinct advisories. Nearly all are **transitive** and clear with a lockfile -refresh + `resolutions`. Notable critical/high: - -- `form-data` `>=3.0.0 <3.0.5` — unsafe random (critical) -- `@babel/traverse` `<7.23.2` — arbitrary code execution (critical) -- `cross-spawn`, `minimatch`, `ws`, `braces`, `semver`, `brace-expansion`, - `lodash`, `js-yaml`, `tmp`, `flatted`, `picomatch` (high) - -Root cause of the long tail: ancient direct deps pulling stale transitive trees. - ---- - -## 2. EOL — Runtime - -| Item | Current | Status | Target | -|------|---------|--------|--------| -| CI Node (`.github/workflows`) | 18.x | **EOL 2025-04-30** | **Node 22 LTS** | -| Local Node | 20.20.2 | EOL 2026-04 | Node 22 LTS | -| `.nvmrc` / `engines` | absent | unpinned drift | add both | - ---- - -## 3. EOL — Direct Dependencies - -| Package(s) | Current | Latest | Status | Notes | -|------------|---------|--------|--------|-------| -| `apollo-client` + `apollo-cache-inmemory` + `apollo-link` + `apollo-link-retry` + `apollo-link-batch-http` | 2.x | `@apollo/client` 4.3.5 | **EOL / unmaintained** | consolidate 5 → 1 pkg; needs graphql ≥16 | -| `graphql` | 14.6 | 17.0.2 | EOL | target **16** (Apollo 4 floor) | -| `typescript` | 3.8 (2020) | 5.9 | EOL | target **5.x** (7.0 = native preview, skip) | -| `eslint` | 8.8 | 10.13 | EOL 2024-10 | flat-config migration; target 9 | -| `@typescript-eslint/*` | 5.x | 8.72 | old | pairs with eslint 9 | -| `jest` / `ts-jest` / `@types/jest` | 27 | 30 / 29 | old | — | -| `@types/node` | 13 | 22 | EOL | match runtime | -| `js-yaml` | 3 | 4.x | **EOL (vuln)** | `safeLoad`→`load` rename | -| `husky` | 7 | 9.1 | old | config change | -| `dotenv` | 7 | 16 | ancient | — | -| `prettier` | 2 | 3.9 | old | reformats tree | -| `lint-staged` | 12 | 17 | old | config shape (partly broken already) | -| `commander` | 5 | 12 (CJS) | old | 12 still CJS-safe | -| `inquirer` | 8 | 14 | old | **ESM-only 9+** → stay at 8 | -| `node-fetch` | 2.6 | 3.3 | maintenance | **ESM-only 3+** → stay at 2 | -| `chalk` | 4 | 6 | old | **ESM-only 5+** → stay at 4 | - -### ⚠️ ESM constraint -Build output is **CommonJS** (`tsconfig: module: commonjs`). `chalk 5+`, -`node-fetch 3+`, `inquirer 9+` are ESM-only. **Decision: Path A — stay CommonJS.** -Keep those on their last CJS major and patch to latest-in-major. No ESM conversion. - ---- - -## 3b. Test Coverage Baseline (before any upgrade) - -`jest --coverage` — 34 tests, 2 suites pass (after `yarn install` fixed a stale -`node_modules` missing `cli-progress`). - -| File | % Stmts | % Branch | Risk | -|------|---------|----------|------| -| **`src/index.ts`** | **41.6** | 41.5 | ⚠️ Apollo client here; most graphClient methods untested | -| `networkRequest.ts` | 73.3 | 62.5 | moderate | -| `queries.ts` | 100 | 100 | safe | -| `util/query`, `util/getProp` | 100 | 83–100 | safe | -| **All files** | **51.5** | 47.5 | — | - -- Apollo init (`index.ts:332-343`) **is** exercised — client-construction swap has a net. -- **~15 graphClient methods are untested** and migrate blind under Apollo 2→4: - `queryGraphQL`, `mutateAlertRule`, `createEntity`, `updateEntity`, `deleteEntity`, - `createRelationship`, `upsertEntityRawData`, `createQuestion`, `updateQuestion`, - `deleteQuestion`, `getIntegrationDefinitions`, `getIntegrationInstances`, - `getIntegrationInstance`, `createIntegrationInstance`, `updateIntegrationInstance`, - `deleteIntegrationInstance`. - -### Test tooling decision -- `@pollyjs/*` is unmaintained. **Keep existing PollyJS tests as-is** (don't rewrite). -- **New coverage uses `nock`** (maintained, CJS-native — no friction with jest 27 / - ts-jest CJS build; intercepts node-fetch 2's HTTP calls). Works now, no upgrade needed. - **`nock` is pinned to `^11` on this branch as an interim** — nock 14's type declarations - don't parse under TypeScript 3.8. It is bumped to `^14` in Phase 1a once TS 5.x lands. -- `msw` / `undici MockAgent` deferred — ESM-first / native-fetch-only, revisit post-upgrade. - ---- - -## 4. Revised Execution Order - -1. **Coverage net (nock)** — ✅ **DONE.** `src/index.nock.test.ts`, 38 tests, all 15 graphClient methods (success + error). `index.ts` coverage 41.6% → **64.6%** stmts, funcs 34% → 68%. 72 tests total (34 PollyJS + 38 nock) green. 3 Apollo-4-sensitive behaviors pinned in `PUBLIC-API.md` §6a. -2. **Migrate yarn → npm** (Section 4a) — ✅ **DONE.** `yarn.lock` removed, `package-lock.json` generated. Scripts/CI/husky/lint-staged swapped. Added `.npmrc` `legacy-peer-deps=true` (bunyan-category's exact `@types/bunyan@1.8.6` peer — removed once Phase 1b lands). CI bumped to Node 22 + setup-node@v4. `npm test` → 72 pass; `npm run build` emits d.ts. npm audit: 322 → **35** (dedup from fresh install). -3. **Phase 0** — ✅ **DONE.** `.nvmrc` (22), `engines: >=22`, `js-yaml 3.13.1 → 4.3.2` - (`safeLoad`→`load` at `j1cli.ts:207`; removes the js-yaml high). Contract unchanged, - 72 tests green, build clean (TS 3.8 intact). **Transitive-vuln `overrides` deferred to - Phase 1a** — `npm audit fix` overreached into the dev tree and pulled a modern - `@types/babel__traverse` that TS 3.8 can't parse (TS1005). Clean fix requires the TS - upgrade first. Residual **prod** vulns (0 critical, 4 high) map to later phases: - `lodash`→Phase 2 (apollo), `moment`→Phase 1b (bunyan optionals), `tmp`/`inquirer`/ - `external-editor`→Phase 1a (inquirer), `brace-expansion`/`minimatch`→Phase 1a overrides. -4. **Phase 1b** — ✅ **DONE.** Replaced `bunyan-category` with `pino ^8.21.0` (pino 8 types - parse under TS 3.8). `inflight` + `mv`/`rimraf@2`/`glob@6` **gone from prod tree**. `Logger` - now an exported structural interface (`JupiterOneClientOptions.logger` backward-compatible) — - approved contract change, baseline updated (see `PUBLIC-API.md` §6b). Removed `.npmrc` - `legacy-peer-deps` (no longer needed — bunyan-category's `@types/bunyan` peer was the cause). - Prod vulns 7 → **4**. 72 tests green, build clean. -5. **Phase 2** — Apollo + graphql (TD-9488 core). Verified by step 1's nock tests. -6. **Phase 1a** — ✅ **DONE** (promoted ahead of Phase 2 — Apollo 3 types need modern TS). - `typescript 3.8 → 5.9` (the `^5.5` range resolves to 5.9.x), `@types/node 13 → 22`, - `jest/@types/jest 27 → 30` + `ts-jest 27 → 29` (ts-jest's peer range covers jest 30), - `@typescript-eslint 5 → 8`, `eslint 8.8 → 8.57` (stayed 8.x — no flat-config churn), - `eslint-plugin-jest 23 → 28`, `eslint-config-prettier 6 → 9` (dropped removed - `prettier/@typescript-eslint`). v8 rules `no-explicit-any` / `no-require-imports` / - `no-unused-vars` pinned to `warn`, with `--max-warnings` on the lint script so the - current count is a ratchet (new violations fail). Also bumps `pino 8 → 10` and - `nock 11 → 14` now that TS 5.9 parses their types. 75 tests green, - lint 0 errors, baseline regenerated under TS 5 (emission-style only). Total audit 44 → 35. -7. **Phase 2** — ✅ **DONE.** Replaced 5 `apollo-*` packages with `@apollo/client ^3.14`, - `graphql 14.6 → 16.9`, `graphql-tag → 2.12.6`. Imports use `@apollo/client/core` + - `/link/retry` + `/link/batch-http` (classic node resolution, CJS-safe). Only `index.ts` - imports + the link setup + the `util/query` `ApolloQueryResult`/`GraphQLFormattedError` - types changed. **Production `npm audit` → 0.** 72 tests green (one PollyJS retry assertion - updated — Apollo 3 dropped the `"Network error: "` prefix). Contract changes approved + - baselined (`PUBLIC-API.md` §6c). TD-9488 graphql@14.7.0 line closed. -8. **Phase 3** — CLI fleet (chalk/node-fetch/inquirer patch) — follow-up. - -### 4a. Yarn → npm migration -- Delete `yarn.lock`; generate `package-lock.json` (`npm install`). -- `package.json`: `resolutions` → **`overrides`** (npm equivalent). -- Scripts: `yarn X` → `npm run X`; `copydist` copies `package-lock.json` not `yarn.lock`; - `audit:fix` `npx yarn-audit-fix` → `npm audit fix`. -- CI workflows: `yarn install --frozen-lockfile` → `npm ci`; `yarn X` → `npm run X`; - `setup-node` cache `yarn` → `npm`. -- `.husky/*` + `lint-staged` (`yarn format`) → npm. -- README install snippet. - ---- - -## 4b. Remediation Plan (phased detail) - -### Phase 0 — Quick wins (low risk) -- Add `.nvmrc` (`22`) + `"engines": { "node": ">=22" }` to `package.json`. -- CI: `actions/setup-node@v4`, `node-version: 22`. -- `js-yaml` 3 → 4 (fixes high vuln; `safeLoad`→`load`, `safeDump`→`dump`). -- Add `resolutions` for transitive critical/high: `form-data`, `@babel/traverse`, - `cross-spawn`, `minimatch`, `ws`, `braces`, `semver`, `brace-expansion`, `tmp`. -- Regenerate `yarn.lock`. -- Fix `${NPM_TOKEN}` npmrc breakage blocking audit tooling. - -### Phase 1 — Tooling (dev-only, no API surface change) -- `typescript` 3.8 → 5.x; `@types/node` 13 → 22. -- `jest` 27 → 30, `ts-jest` → 29, `@types/jest` → 30. -- `eslint` 8 → 9 (flat config) + `@typescript-eslint` 5 → 8. -- `prettier` 2 → 3; `husky` 7 → 9; `lint-staged` 12 → 17; `dotenv` 7 → 16. - -### Phase 2 — Apollo + GraphQL (runtime, core risk) -- Replace 5 `apollo-*` pkgs with `@apollo/client` 3 + `graphql` 16. - (`@apollo/client` 4 is current but ESM/`node16`-resolution-oriented; v3 chosen for the - CommonJS build. **v4 upgrade carried as a remaining EOL follow-up** — see §5.) -- Rewrite only `src/index.ts:1-5` (imports) and `src/index.ts:332-343` - (ApolloLink / RetryLink / BatchHttpLink / InMemoryCache setup — ~15 isolated lines). -- Preserve batch-http + retry behavior. -- Validate against `src/index.test.ts` + `src/index.new.test.ts` (PollyJS fixtures). - -### Phase 3 — CLI deps (Path A — CJS) -- `commander` 5 → 12. -- Patch to latest-in-major: `chalk@4`, `node-fetch@2`, `inquirer@8`. -- No ESM conversion. - -### Phase 4 — Verify & release -- `yarn audit` → expect near-zero high/critical. -- `yarn test` (lint + type-check + jest) green. -- `yarn build` + smoke-test `bin/j1`. -- Release **v3.0.0** (Node 22 floor + Apollo swap = breaking). - ---- - -## 5. Risk Notes -- **Phase 2 is the only runtime-behavior risk.** Apollo 2→3 cache/link API changed; - rely on the nock suite + existing PollyJS wire fixtures to catch regressions. -- **Path B (ESM conversion)** deferred — would drop `node-fetch` for native `fetch` - (Node 22 global) and take latest chalk/inquirer, but breaks CJS consumers. Separate effort. -- Phases 0–1 shippable independently as a patch release before the v3 breaking bundle. - -## 6. Remaining EOL follow-ups (post-v3) -- **`@apollo/client` 3 → 4** — 3.x is maintenance-only; 4.x is current but ESM/`node16`. -- **CLI deps** — `chalk` 4→6, `node-fetch` 2→3, `inquirer` 8→14, `commander` 5→15 (all - ESM-only latests; Path B / ESM conversion). -- **Dev tooling** — `eslint` 8→10 (flat config), `lint-staged` 12→17, `prettier` 2→3, - `husky` 7→9, `dotenv` 7→18. diff --git a/PUBLIC-API.baseline.d.ts b/PUBLIC-API.baseline.d.ts deleted file mode 100644 index bafca37..0000000 --- a/PUBLIC-API.baseline.d.ts +++ /dev/null @@ -1,248 +0,0 @@ -import { ApolloClient, QueryOptions } from '@apollo/client/core'; -import { Response as FetchResponse } from 'node-fetch'; -import { Entity, EntityForSync, Relationship, RelationshipForSync, IntegrationDefinition, IntegrationInstance, ListIntegrationInstancesOptions, EntitySource } from './types'; -import { QueryTypes } from './util/query'; -export declare class FetchError extends Error { - httpStatusCode: number; - responseBody: string; - response: FetchResponse; - method: string; - url: string; - nameForLogging?: string; - constructor(options: { - responseBody: string; - response: FetchResponse; - method: string; - url: string; - nameForLogging?: string; - }); -} -export interface JupiterOneEntityMetadata { - _rawDataHashes?: string; - _integrationDefinitionId?: string; - _integrationInstanceId?: string; - _integrationClass?: string | string[]; - _integrationType?: string; - _integrationName?: string; - _createdOn: number; - _beginOn: number; - _version: number; - _accountId: string; - _deleted: boolean; - _source: EntitySource; - _id: string; - _key: string; - _class: string[]; - _type: string | string[]; - displayName?: string; -} -export interface JupiterOneEntity { - entity: JupiterOneEntityMetadata; - properties: any; -} -export interface QueryResult { - id: string; - entity: object; - properties: object; -} -/** - * Minimal structural logger interface. Any bunyan/pino-style logger satisfies it, - * so consumers can continue passing their existing logger. Each method accepts - * either `(msg)` or `(mergeObject, msg)`, matching both bunyan and pino. - */ -export type LogFn = (objOrMsg: unknown, msg?: string) => void; -export interface Logger { - trace(objOrMsg: unknown, msg?: string): void; - debug(objOrMsg: unknown, msg?: string): void; - info(objOrMsg: unknown, msg?: string): void; - warn(objOrMsg: unknown, msg?: string): void; - error(objOrMsg: unknown, msg?: string): void; - fatal(objOrMsg: unknown, msg?: string): void; -} -export interface JupiterOneClientOptions { - account: string; - accessToken?: string; - dev?: boolean; - useRulesEndpoint?: boolean; - apiBaseUrl?: string; - logger?: Logger; -} -export declare enum SyncJobStatus { - AWAITING_UPLOADS = "AWAITING_UPLOADS", - FINALIZE_PENDING = "FINALIZE_PENDING", - FINALIZING_ENTITIES = "FINALIZING_ENTITIES", - FINALIZING_RELATIONSHIPS = "FINALIZING_RELATIONSHIPS", - ABORTED = "ABORTED", - FINISHED = "FINISHED", - UNKNOWN = "UNKNOWN", - ERROR_BAD_DATA = "ERROR_BAD_DATA", - ERROR_UNEXPECTED_FAILURE = "ERROR_UNEXPECTED_FAILURE" -} -export type SyncJob = { - source: SyncJobSources; - scope: string; - accountId: string; - id: string; - status: SyncJobStatus; - done: boolean; - startTimestamp: number; - numEntitiesUploaded: number; - numEntitiesCreated: number; - numEntitiesUpdated: number; - numEntitiesDeleted: number; - numEntityCreateErrors: number; - numEntityUpdateErrors: number; - numEntityDeleteErrors: number; - numEntityRawDataEntriesUploaded: number; - numEntityRawDataEntriesCreated: number; - numEntityRawDataEntriesUpdated: number; - numEntityRawDataEntriesDeleted: number; - numEntityRawDataEntryCreateErrors: number; - numEntityRawDataEntryUpdateErrors: number; - numEntityRawDataEntryDeleteErrors: number; - numRelationshipsUploaded: number; - numRelationshipsCreated: number; - numRelationshipsUpdated: number; - numRelationshipsDeleted: number; - numRelationshipCreateErrors: number; - numRelationshipUpdateErrors: number; - numRelationshipDeleteErrors: number; - numRelationshipRawDataEntriesUploaded: number; - numRelationshipRawDataEntriesCreated: number; - numRelationshipRawDataEntriesUpdated: number; - numRelationshipRawDataEntriesDeleted: number; - numRelationshipRawDataEntryCreateErrors: number; - numRelationshipRawDataEntryUpdateErrors: number; - numRelationshipRawDataEntryDeleteErrors: number; - numMappedRelationshipsCreated: number; - numMappedRelationshipsUpdated: number; - numMappedRelationshipsDeleted: number; - numMappedRelationshipCreateErrors: number; - numMappedRelationshipUpdateErrors: number; - numMappedRelationshipDeleteErrors: number; - syncMode: SyncJobModes; -}; -export type SyncJobOptions = { - source?: SyncJobSources; - scope?: string; - syncMode?: string; - integrationInstanceId?: string; -}; -export declare enum SyncJobSources { - API = "api", - INTEGRATION_MANAGED = "integration-managed" -} -export declare enum SyncJobModes { - DIFF = "DIFF", - CREATE_OR_UPDATE = "CREATE_OR_UPDATE" -} -export type SyncJobResponse = { - job: SyncJob; -}; -export type PublishEventsResponse = { - events: Array<{ - id: string; - name: string; - description: string; - createDate: number; - }>; -}; -export type ObjectDeletion = { - _id: string; -}; -export type GraphObjectDeletionPayload = { - deleteEntities: ObjectDeletion[]; - deleteRelationships: ObjectDeletion[]; -}; -export type SyncJobResult = { - syncJobId: string; - finalizeResult: SyncJobResponse; -}; -export declare class JupiterOneClient { - graphClient: ApolloClient; - headers?: Record; - account: string; - accessToken: string; - useRulesEndpoint: boolean; - apiUrl: string; - queryEndpoint: string; - rulesEndpoint: string; - logger: Logger; - constructor({ account, accessToken, dev, useRulesEndpoint, apiBaseUrl, logger, }: JupiterOneClientOptions); - init(): Promise; - queryV1(j1ql: string, options?: QueryOptions | Record, - /** - * include a progress bar to show progress of getting data. - */ - showProgress?: boolean, - /** because this method queries repeatedly with its own LIMIT, - * this limits the looping to stop after at least {stopAfter} results are found - * @deprecated This property is no longer supported. - */ - stopAfter?: number, - /** same as above, this gives more fine-grained control over the starting point of the query, - * since this method controls the `SKIP` clause in the query - * @deprecated This property is no longer supported. - */ - startPage?: number): Promise; - queryGraphQL(query: any): Promise>; - mutateAlertRule(rule: any, update: any): Promise; - createEntity(key: string, type: string, classLabels: string[], properties: any): Promise; - updateEntity(entityId: string, properties: any): Promise; - deleteEntity(entityId: string, hardDelete?: boolean): Promise; - createRelationship(relationshipKey: string, relationshipType: string, relationshipClass: string, fromEntityId: string, toEntityId: string, properties: any): Promise; - upsertEntityRawData(entityId: string, name: string, contentType: string, data: any): Promise; - createQuestion(question: any): Promise; - updateQuestion(question: any): Promise; - deleteQuestion(questionId: string): Promise; - integrationDefinitions: { - list: () => Promise>; - }; - integrationInstances: { - list: (options?: ListIntegrationInstancesOptions) => Promise>; - get: (id: string) => Promise>; - create: (instance: Omit, "id" | "accountId">) => Promise>; - update: (id: string, update: Partial, "id" | "accountId" | "integrationDefinitionId">>) => Promise>; - delete: (id: string) => Promise<{ - success?: boolean; - }>; - }; - startSyncJob(options: SyncJobOptions): Promise; - uploadGraphObjectsForDeleteSyncJob(options: { - syncJobId: string; - entities?: Entity[]; - relationships?: Relationship[]; - }): Promise; - uploadGraphObjectsForSyncJob(options: { - syncJobId: string; - entities?: EntityForSync[]; - relationships?: RelationshipForSync[]; - }): Promise; - finalizeSyncJob(options: { - syncJobId: string; - }): Promise; - abortSyncJob(options: { - syncJobId: string; - reason: string; - }): Promise; - publishEvents(options: { - syncJobId: string; - events: Array<{ - name: string; - description: string; - }>; - }): Promise; - fetchSyncJobStatus(options: { - syncJobId: string; - }): Promise; - private areValidSyncJobOptions; - bulkUpload(data: { - syncJobOptions: SyncJobOptions; - entities?: EntityForSync[]; - relationships?: RelationshipForSync[]; - }): Promise; - bulkDelete(data: { - entities?: Entity[]; - relationships?: Relationship[]; - }): Promise; -} diff --git a/PUBLIC-API.md b/PUBLIC-API.md deleted file mode 100644 index 97ba367..0000000 --- a/PUBLIC-API.md +++ /dev/null @@ -1,241 +0,0 @@ -# Public API Contract — `@jupiterone/jupiterone-client-nodejs` - -**Frozen at:** v2.1.2 (2026-10-20), commit baseline before TD-9488 dep upgrades. -**Purpose:** Pin the consumer-facing surface so post-upgrade changes can be diffed -against this. **Nothing here may change** through the EOL upgrade (Apollo 2→3, graphql -14→16, node-fetch, etc.). Any divergence = breaking change → requires a major bump + -changelog note. - -> Validation: after each phase, re-derive this surface and diff against the frozen baseline: -> ```sh -> npm run build # emits dist/index.d.ts -> diff PUBLIC-API.baseline.d.ts dist/index.d.ts # must be empty, OR every diff consciously approved -> ``` -> `PUBLIC-API.baseline.d.ts` = the generated `.d.ts` snapshot (regenerated under TS 5 in -> Phase 1a — emission-style only: `export type` vs `export declare type`, `` vs -> ``, symbolic `Omit<…>` vs TS-3.8's expanded `Pick<…>`. Semantically identical). -> Expected intentional diffs under the upgrade: the import sources on lines 1-3 -> (`apollo-client`→`@apollo/client`, `node-fetch` type path). Any OTHER diff = unplanned break. - ---- - -## 1. Package entry points - -| Field | Value | -|-------|-------| -| `main` | `dist/index.js` (CommonJS) | -| `types` | `dist/index.d.ts` | -| `bin` | `j1` → `bin/j1` → `require('../dist/j1cli.js')` | -| Module format | **CommonJS** (`tsconfig: module: commonjs`, `target: es2018`) | - -Import path for consumers: `import { JupiterOneClient, ... } from '@jupiterone/jupiterone-client-nodejs'` - ---- - -## 2. Programmatic API — exported symbols (from `src/index.ts`) - -### Classes -- `class JupiterOneClient` — main client (see §3). -- `class FetchError extends Error` — fields: `httpStatusCode: number`, `responseBody: string`, `response: FetchResponse`, `method: string`, `url: string`. - -### Interfaces -- `JupiterOneClientOptions` — constructor arg (see §3.1). -- `JupiterOneEntityMetadata` — `_createdOn, _beginOn, _version, _accountId, _deleted, _source, _id, _key, _class: string[], _type: string | string[]`, … -- `JupiterOneEntity` — `{ entity: JupiterOneEntityMetadata; properties: any }`. -- `QueryResult` — `{ id: string; entity: object; properties: object }`. - -### Enums -- `SyncJobStatus` — `AWAITING_UPLOADS | FINALIZE_PENDING | FINALIZING_ENTITIES | FINALIZING_RELATIONSHIPS | ABORTED | FINISHED | UNKNOWN | ERROR_BAD_DATA | ERROR_UNEXPECTED_FAILURE`. -- `SyncJobSources` — `API = 'api' | INTEGRATION_MANAGED = 'integration-managed'`. -- `SyncJobModes` — `DIFF = 'DIFF' | CREATE_OR_UPDATE = 'CREATE_OR_UPDATE'`. - -### Types -- `SyncJob` — large metric-counter object (entity/relationship/rawData create/update/delete counts). See `src/index.ts:196`. -- `SyncJobOptions` — `{ source?: SyncJobSources; scope?: string; syncMode?: string; integrationInstanceId?: string }`. -- `SyncJobResponse` — `{ job: SyncJob }`. -- `PublishEventsResponse` — `{ events: Array<{ id; name; description; createDate: number }> }`. -- `ObjectDeletion` — `{ _id: string }`. -- `GraphObjectDeletionPayload` — `{ deleteEntities: ObjectDeletion[]; deleteRelationships: ObjectDeletion[] }`. -- `SyncJobResult` — `{ syncJobId: string; finalizeResult: SyncJobResponse }`. - -### Re-exported from `src/types.ts` -`EntityPropertyValuePrimitive`, `EntityPropertyValue`, `EntityAdditionalProperties`, -`Entity`, `RelationshipPropertyValuePrimitive`, `RelationshipPropertyValue`, -`RelationshipAdditionalProperties`, `Relationship`, `GraphObject`, `EntitySource`, -`EntityForSync`, `RelationshipForSync`, `IntegrationPollingInterval`, -`IntegrationPollingIntervalCronExpression`, `IntegrationInstanceConfig`, -`IntegrationDefinition`, `ListIntegrationDefinitions`, `IntegrationInstance`, -`ListIntegrationInstancesOptions`, `PageInfo`, `ListIntegrationInstances`. - ---- - -## 3. `JupiterOneClient` class - -### 3.1 Constructor -```ts -new JupiterOneClient(options: JupiterOneClientOptions) - -interface JupiterOneClientOptions { - account: string; - accessToken?: string; - dev?: boolean; // true → https://api.dev.jupiterone.io - useRulesEndpoint?: boolean; // true → /rules/graphql instead of /graphql - apiBaseUrl?: string; // overrides dev/prod base URL - logger?: Logger; // bunyan-category Logger -} -``` -Public fields: `graphClient: ApolloClient`, `headers?`, `account`, `accessToken`, -`useRulesEndpoint`, `apiUrl`, `queryEndpoint`, `rulesEndpoint`, `logger`. - -⚠️ **`graphClient: ApolloClient` is a public field.** Apollo 2→3 changes the -`ApolloClient` type. If any consumer touches `client.graphClient` directly, that is a -breaking type change — flag it. (Recommend: keep the field name/shape, accept the type swap.) - -### 3.2 Lifecycle -- `init(): Promise` — builds headers + Apollo link (RetryLink + BatchHttpLink) + InMemoryCache. **Must be awaited before any method.** Usage: `await new JupiterOneClient({...}).init()`. - -### 3.3 Query methods -- `queryV1(j1ql: string, options?: QueryOptions | Record, showProgress?: boolean, stopAfter?: number, ...): Promise<...>` — primary J1QL query (handles deferred S3 responses). `stopAfter` **@deprecated**. -- `queryGraphQL(query: any): Promise` — raw GraphQL passthrough. - -### 3.4 Entity / relationship mutations -- `createEntity(key: string, type: string, classLabels: string[], properties: any): Promise` → `data.createEntity`. -- `updateEntity(entityId: string, properties: any): Promise`. -- `deleteEntity(entityId: string, hardDelete?: boolean): Promise`. -- `createRelationship(relationshipKey, relationshipType, relationshipClass, fromEntityId, toEntityId, properties): Promise` → `data.createRelationship`. -- `upsertEntityRawData(entityId: string, name: string, contentType: string, data: any): Promise` → `data.upsertEntityRawData.status`. - -### 3.5 Alert rule / question mutations -- `mutateAlertRule(rule: any, update: any): Promise`. -- `createQuestion(question: any): Promise`. -- `updateQuestion(question: any): Promise`. -- `deleteQuestion(questionId: string): Promise`. - -### 3.6 Integration namespaces (object properties, not methods) -- `integrationDefinitions.list(): Promise>`. -- `integrationInstances.list(options?): Promise>`. -- `integrationInstances.get(id: string)`. -- `integrationInstances.create(instance: Omit, 'id' | 'accountId'>)`. -- `integrationInstances.update(id, instance: Partial>)`. -- `integrationInstances.delete(id: string)` → `{ success?: boolean }`. - -### 3.7 Sync-job methods (REST via `makeFetchRequest`, not Apollo) -- `startSyncJob(options: SyncJobOptions): Promise`. -- `uploadGraphObjectsForSyncJob(options: { syncJobId: string; entities?: EntityForSync[]; relationships?: RelationshipForSync[] }): Promise`. -- `uploadGraphObjectsForDeleteSyncJob(options: { syncJobId: string; entities?: Entity[]; relationships?: Relationship[] }): Promise`. -- `finalizeSyncJob(options: { syncJobId: string }): Promise`. -- `abortSyncJob(options: { syncJobId: string; reason: string }): Promise`. -- `publishEvents(options: { syncJobId: string; events: Array<{ name: string; description: string }> }): Promise`. -- `fetchSyncJobStatus(options: { syncJobId: string }): Promise`. -- `bulkUpload(data: { syncJobOptions: SyncJobOptions; entities?: EntityForSync[]; relationships?: RelationshipForSync[] }): Promise`. -- `bulkDelete(data: { entities?: Entity[]; relationships?: Relationship[] }): Promise`. - ---- - -## 4. HTTP wire contract (must not change) - -Apollo swap + node-fetch change could alter request shape. Lock these: - -### GraphQL (Apollo `BatchHttpLink`) -- `POST {apiUrl}/graphql` (or `/rules/graphql` when `useRulesEndpoint`). -- **Batched**: request body is a JSON **array** of operations; response is a JSON **array** of results. -- Headers: `Authorization: Bearer `, `JupiterOne-Account: `, `content-type: application/json`. -- RetryLink: initial 2000ms, max 5000ms, jitter. - -### REST (sync/persister, via `makeFetchRequest` / node-fetch) -- `POST {apiUrl}/persister/synchronization/jobs` — start. -- `POST .../jobs/{syncJobId}/upload` — upload objects. -- `POST .../jobs/{syncJobId}/finalize` — finalize. -- `POST .../jobs/{syncJobId}/abort` — abort. -- `POST .../jobs/{syncJobId}/events` — publish events. -- `GET .../jobs/{syncJobId}` — status. - -### Base URLs -- prod: `https://api.us.jupiterone.io` · dev: `https://api.dev.jupiterone.io` · override: `apiBaseUrl`. - ---- - -## 5. CLI contract — `j1` (`src/j1cli.ts`) - -Supported operations (`-o/--operation`): `create`, `update`, `upsert` (entities only), -`delete`, `bulk-delete`, `provision-alert-rule-pack`. - -Flags (commander): -| Flag | Meaning | -|------|---------| -| `-a, --account ` | account name | -| `-u, --user ` | user email | -| `-k, --key ` | API token (or env `J1_API_TOKEN`) | -| `-q, --query ` | execute a query | -| `-o, --operation ` | one of the supported operations | -| `-e, --entity` | entity operations | -| `-r, --relationship` | relationship operations | -| `-l, --alert` | alert rule operations | -| `-w, --question` | question operations | -| `-f, --input-file ` | input JSON / alert-rule-pack filename | -| `--hard-delete` | force hard delete | -| `--delete-duplicates` | delete duplicate entities w/ identical keys | -| `--output-file ` | write query result (default `results.json`) | -| `--api-base-url ` | override base URL | - ---- - -## 6. Known-sensitive-to-upgrade items (watch during diff) -1. `graphClient: ApolloClient` public field type — Apollo 2→3 type change. -2. GraphQL batched array request/response shape — BatchHttpLink behavior. -3. Return shapes `res.data.` unwrapping — Apollo 3 may change `errors`/`data` nullability. -4. `queryV1` deferred-S3 flow — node-fetch version swap affects the S3 GET. -5. `Logger` type in `JupiterOneClientOptions` — tied to `bunyan-category` (inflight chain). - -### 6a. Pinned behaviors (verified by `src/index.nock.test.ts`) -These were the pre-migration watch-items (written when a v4 target was assumed; the stack -shipped **@apollo/client 3** — see §6c for the actual outcome). They are **observable -behaviors** — including quirks/bugs — that the migration must not change silently. -**Status: all verified unchanged under Apollo 3** by the nock suite. -- **`deleteEntity` resolves to `undefined` on success.** Method reads `res.data.deleteEntity` - but the mutation's root field is `deleteEntityV2`, so the read is always `undefined`. - Pre-existing bug; pinned so the migration doesn't accidentally "fix" (change) it. -- **GraphQL error paths reject with the underlying `ApolloError`, not the client's - `throw new Error('JupiterOne returned error(s)...')` wrapper.** Apollo default - `errorPolicy: 'none'` rejects before the `if (res.errors)` guard runs → those wrapper - branches are dead code. **Exception:** `upsertEntityRawData` catches + re-wraps as - `Unable to store raw template data for ...`. **Unchanged under Apollo 3** (verified). -- **`integrationInstances.update` / `.delete` pass the `query` keyword into `mutate()`.** - apollo-client 2.6 tolerated it; **✅ RESOLVED — @apollo/client 3 also accepts it**: both - methods are covered by `src/index.nock.test.ts` and pass under Apollo 3. (A future - @apollo/client 4 upgrade should re-verify, as v4 is stricter.) -- `integrationDefinitions.list` uses `errorPolicy: 'all'` → collects partial errors without throwing. - -### 6c. Approved contract changes — Phase 2 (apollo-client 2 → @apollo/client 3) -**Baseline `.d.ts` updated.** `graphql 14 → 16`; the 5 `apollo-*` packages replaced by -`@apollo/client ^3.14`. Consumer-visible changes: -- **Type import source:** `ApolloClient` / `QueryOptions` now from `@apollo/client/core` - (was `apollo-client`). `graphClient: ApolloClient` field type updated accordingly. -- **`queryGraphQL` return type:** `ApolloQueryResult` → `InteropApolloQueryResult`. - Pure rename — `InteropApolloQueryResult` is field-for-field identical to `ApolloQueryResult` - (`data`, `errors?`, `error?`, `loading`, `networkStatus`, `partial?`) and mutually - assignable; it's a deprecated bridge type for a future v4. `data` stays non-optional, so - no new null-safety break. Only matters if you annotate the return type explicitly. -- **Error message string:** @apollo/client 3 dropped the `"Network error: "` prefix on - network-error messages — a thrown error is now e.g. `Response not successful: Received - status code 401` (was `Network error: Response not successful...`). Consumers matching on - that prefix must adjust. (PollyJS retry test updated to match.) -- **`QueryResults.errors`** element type changed `GraphQLError` → `GraphQLFormattedError` - (graphql 16 split the full error object from its serialized form; apollo returns the - formatted one). Affects `integrationDefinitions.list` / `integrationInstances.list` return - types. Runtime values unchanged — same `{ message, locations, path }` shape. -- Production `npm audit` is now **0** (apollo removal cleared the lodash/minimatch chain). - -> **Follow-up tracked:** `@apollo/client` 3.x is itself maintenance-only (4.x is current). -> v3 was chosen deliberately — 4.x is ESM/`node16`-resolution-oriented and a harder fit for -> this CommonJS build — but a future `@apollo/client` 4 upgrade remains an open EOL item. - -### 6b. Approved contract change — Phase 1b (bunyan → pino) -**Baseline `.d.ts` updated** to reflect this. `bunyan-category` replaced with `pino` to -remove the `mv → rimraf → glob → inflight` chain from the consumer tree (TD-9488). -- **Before:** `logger?: Logger` where `Logger` = default import from `bunyan-category` (not exported). -- **After:** `Logger` is a **structural interface exported from the package** - (`{ trace, debug, info, warn, error, fatal }`, each `(objOrMsg, msg?) => void`), plus - exported `LogFn`. Any bunyan/pino-style logger still satisfies it. -- **Break risk:** only a consumer who imported bunyan's `Logger` type and used - bunyan methods beyond those six. Runtime log output drops the bunyan `category` field. diff --git a/tsconfig.dist.json b/tsconfig.dist.json index c1a9a64..91bbfb5 100644 --- a/tsconfig.dist.json +++ b/tsconfig.dist.json @@ -1,4 +1,4 @@ { "extends": "./tsconfig.json", - "exclude": ["dist", "examples", "**/*.test.ts", "PUBLIC-API.baseline.d.ts"] + "exclude": ["dist", "examples", "**/*.test.ts"] } diff --git a/tsconfig.json b/tsconfig.json index f5fbfc7..0f4aaad 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,5 +10,5 @@ "esModuleInterop": true, "resolveJsonModule": true }, - "exclude": ["dist", "examples", "PUBLIC-API.baseline.d.ts"] + "exclude": ["dist", "examples"] }