|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +Guidance for working in this repo with Claude Code (or any agent). |
| 4 | + |
| 5 | +## What this repo is |
| 6 | + |
| 7 | +Official Pipedream Java SDK. Most of the source tree is generated by **Fern** |
| 8 | +(generator: `fernapi/fern-java-sdk`, version pinned in `.fern/metadata.json`). A |
| 9 | +small set of files are hand-maintained on top of the generated code; they are |
| 10 | +listed explicitly in `.fernignore` and are the only files Fern won't overwrite |
| 11 | +on regeneration. |
| 12 | + |
| 13 | +## The single most important rule |
| 14 | + |
| 15 | +**If a file is not in `.fernignore`, do not edit it.** Fern owns it and the next |
| 16 | +regeneration will discard your changes. This includes: |
| 17 | + |
| 18 | +- `build.gradle`, `settings.gradle`, `gradle/**` |
| 19 | +- The entire `.fern/` directory (incl. `.fern/metadata.json`) |
| 20 | +- `README.md` |
| 21 | +- Every file under `src/main/java/com/pipedream/api/` and |
| 22 | + `src/test/java/com/pipedream/api/` not in `.fernignore` |
| 23 | + |
| 24 | +If you need to change something in a generated file, the change has to happen |
| 25 | +**upstream of this repo** — in the Fern API definition / generator config the |
| 26 | +bot reads from — and arrive here via the next regeneration. Locally, your |
| 27 | +options are: add the file to `.fernignore` (and own it going forward), or absorb |
| 28 | +the change into a hand-maintained wrapper. When in doubt, ask. |
| 29 | + |
| 30 | +## What's hand-maintained |
| 31 | + |
| 32 | +`.fernignore` is the source of truth. As of this writing: |
| 33 | + |
| 34 | +| Path | Purpose | |
| 35 | +| ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
| 36 | +| `src/main/java/com/pipedream/api/PipedreamClient.java` | Public sync entry point. Wraps generated `BaseClient`, adds `workflows()` and `rawAccessToken()`, exposes the env-var-aware `builder()` factory. | |
| 37 | +| `src/main/java/com/pipedream/api/PipedreamClientBuilder.java` | Backward-compatible builder. Owns the `clientId/clientSecret/token/scope` fluent surface and orchestrates OAuth (`OAuthTokenSupplier`) or token-header auth in `build()`. | |
| 38 | +| `src/main/java/com/pipedream/api/AsyncPipedreamClient.java`, `AsyncPipedreamClientBuilder.java` | Async mirrors of the above. | |
| 39 | +| `src/main/java/com/pipedream/api/BaseClientBuilder.java`, `AsyncBaseClientBuilder.java` | Re-protected after the 3.27.6 regen so the generic `<T extends ...<T>>` self-type the Pipedream subclasses depend on cannot silently disappear in a future regen. | |
| 40 | +| `src/main/java/com/pipedream/api/resources/proxy/**` | Custom HTTP proxy implementation (request types, sync/async clients, `ProxyResponse`). | |
| 41 | +| `src/main/java/com/pipedream/api/resources/workflows/**`, `types/HTTPAuthType.java` | Custom workflow invocation surface. | |
| 42 | +| `src/test/java/com/pipedream/api/OauthTokensWireTest.java`, `ProxyClientWireTest.java`, `TokensWireTest.java` | Wire tests we own. | |
| 43 | +| `src/test/java/com/pipedream/api/testutil/MockWebServer.java`, `MockResponse.java`, `RecordedRequest.java` | JDK `HttpServer`-based mock-server shim. Replaces `okhttp3.mockwebserver` so we don't need to thread an extra test dep through the Fern-managed `build.gradle`. | |
| 44 | +| `reference.md`, `LICENSE`, `.github/workflows/auto-close-empty-prs.yml` | Docs / legal / CI we control. | |
| 45 | + |
| 46 | +If you add a new hand-maintained file, list it in `.fernignore` in the same |
| 47 | +commit. |
| 48 | + |
| 49 | +## Adding dependencies |
| 50 | + |
| 51 | +`build.gradle` and `.fern/metadata.json` (where the `custom-dependencies` array |
| 52 | +lives) are both Fern-managed — neither can be edited in this repo. Adding a |
| 53 | +runtime or test dependency means changing the upstream Fern config and waiting |
| 54 | +for the next regen. |
| 55 | + |
| 56 | +**If you need the dep in this repo before then, do not "bridge" by editing |
| 57 | +`build.gradle` — find another path.** For test-only dependencies, prefer the JDK |
| 58 | +or write a shim in `src/test/java/com/pipedream/api/testutil/` (already |
| 59 | +`.fernignore`-protected). The existing `MockWebServer` shim is the worked |
| 60 | +example: it sidesteps `okhttp3.mockwebserver` so we don't need a test dep at |
| 61 | +all. |
| 62 | + |
| 63 | +## Versioning |
| 64 | + |
| 65 | +The version is propagated by Fern from upstream config into `build.gradle` and |
| 66 | +`.fern/metadata.json`'s `sdkVersion`. Don't bump it in either place locally — |
| 67 | +the bump has to happen upstream and ride in via regen. |
| 68 | + |
| 69 | +This SDK currently follows the convention: any net-new public API surface is a |
| 70 | +**minor bump** (no breaking change), any removal/rename is a **major bump**. |
| 71 | +Backward compatibility for the `PipedreamClient.builder()` chain is a hard |
| 72 | +requirement — see the next section. |
| 73 | + |
| 74 | +## Public API contract (PipedreamClient) |
| 75 | + |
| 76 | +The supported customer-facing API on the sync side (mirror exists on async): |
| 77 | + |
| 78 | +```java |
| 79 | +// Env-aware: reads PIPEDREAM_CLIENT_ID, _CLIENT_SECRET, _BASE_URL, |
| 80 | +// _PROJECT_ENVIRONMENT, _PROJECT_ID |
| 81 | +PipedreamClient.builder() |
| 82 | + .clientId("...") // override env default |
| 83 | + .clientSecret("...") // override env default |
| 84 | + .scope("read:accounts") // OAuth progressive scopes (added 1.2.0) |
| 85 | + .token("eyJ...") // bypass OAuth entirely; precedence over creds |
| 86 | + .url("https://...") // alias: baseUrl(...) |
| 87 | + .projectId("...") |
| 88 | + .projectEnvironment("...") |
| 89 | + .timeout(60).maxRetries(2) |
| 90 | + .httpClient(myOkHttpClient) |
| 91 | + .addHeader("X-Foo", "bar") |
| 92 | + .build(); |
| 93 | +``` |
| 94 | + |
| 95 | +Build-time precedence in `PipedreamClientBuilder.build()`: |
| 96 | + |
| 97 | +1. `token != null` → `Authorization: Bearer <token>` |
| 98 | +2. else `clientId != null && clientSecret != null` → OAuth client-credentials |
| 99 | + via `OAuthTokenSupplier` + `OauthTokensClient` (lazy on first request) |
| 100 | +3. else → no auth header |
| 101 | + |
| 102 | +`.clientId(null) / .clientSecret(null)` is a supported way to suppress the |
| 103 | +env-var defaults for tests. |
| 104 | + |
| 105 | +## Why we don't add `withCredentials`/`withToken` to PipedreamClient |
| 106 | + |
| 107 | +The generated `BaseClient` exposes `static withCredentials(...)` and `static |
| 108 | +withToken(...)` returning `BaseClientBuilder._CredentialsAuth` and `_TokenAuth`. |
| 109 | +Java's static-method-hiding rules require any same-name static on a subclass to |
| 110 | +have a covariant return type. `PipedreamClientBuilder` is not a subtype of |
| 111 | +`_CredentialsAuth` / `_TokenAuth`, so adding those statics on `PipedreamClient` |
| 112 | +won't compile. The hybrid surface above (env-aware `builder()` plus |
| 113 | +`.token(...)` and `.scope(...)`) is the workaround. |
| 114 | + |
| 115 | +Customers wanting the Fern-native factory style can still call |
| 116 | +`BaseClient.withCredentials(...)` / `BaseClient.withToken(...)` directly — they |
| 117 | +just lose the `workflows()` accessor. |
| 118 | + |
| 119 | +## Testing |
| 120 | + |
| 121 | +```bash |
| 122 | +./gradlew test # full unit + wire suite (28 tests as of 1.2.0) |
| 123 | +./gradlew compileJava # main sources only |
| 124 | +./gradlew clean test # forces re-run; useful when fiddling with test code |
| 125 | +``` |
| 126 | + |
| 127 | +Wire tests use the `com.pipedream.api.testutil` shim — `MockWebServer` is backed |
| 128 | +by `com.sun.net.httpserver.HttpServer`, no external dep. The shim only |
| 129 | +implements the surface the tests use (`start`, `shutdown`, `url`, `enqueue`, |
| 130 | +`takeRequest`, plus `MockResponse` setters and `RecordedRequest` getters). |
| 131 | +Extend it if a test needs more. |
| 132 | + |
| 133 | +When writing a wire test, prefer constructing the client via |
| 134 | +`PipedreamClient.builder().url(server.url("/").toString()).build()` over `new |
| 135 | +PipedreamClientBuilder()` — it's the public factory and matches how customers |
| 136 | +wire things up. Heads up: `builder()` reads `PIPEDREAM_CLIENT_ID` / |
| 137 | +`PIPEDREAM_CLIENT_SECRET` from the environment, so if those are set on the |
| 138 | +machine the OAuth flow will fire on the first request and consume an extra |
| 139 | +queued response. CI is clean; locally, unset them or pass explicit |
| 140 | +`.clientId(null).clientSecret(null)`. |
| 141 | + |
| 142 | +`./gradlew spotlessCheck` is **broken** on newer JDKs (Palantir Java Format |
| 143 | +calls `JCImport.getQualifiedIdentifier()` which the JDK has removed). It was |
| 144 | +broken before the recent regen too. Don't try to "fix" it as part of an |
| 145 | +unrelated change — it's a separate cleanup that needs the spotless / PJF plugin |
| 146 | +versions bumped, and those live in the Fern-generated `build.gradle`. |
| 147 | + |
| 148 | +## Java compatibility |
| 149 | + |
| 150 | +`sourceCompatibility` / `targetCompatibility` are pinned at Java 8. Keep new |
| 151 | +hand-written code Java-8-compatible: no `var`, no `record`, no `List.of`, no |
| 152 | +`InputStream.readAllBytes()` (see the helper in `ProxyClientWireTest`). |
| 153 | + |
| 154 | +## After a Fern regen |
| 155 | + |
| 156 | +When a Fern bot PR lands, expect: |
| 157 | + |
| 158 | +1. `build.gradle` to change (deps, version, sometimes plugins). |
| 159 | +2. `README.md` to be regenerated wholesale. |
| 160 | +3. Generated source under `src/main/java/com/pipedream/api/` to churn. |
| 161 | +4. The files in `.fernignore` to stay untouched. |
| 162 | + |
| 163 | +Sanity checks before merging a regen PR: |
| 164 | + |
| 165 | +- `./gradlew compileJava` is green. |
| 166 | +- `./gradlew test` is green. |
| 167 | +- The public API on `PipedreamClient` / `AsyncPipedreamClient` (see contract |
| 168 | + section above) still compiles. Inspect any change to `BaseClient` / |
| 169 | + `AsyncBaseClient` static factories carefully — those are what the wrappers |
| 170 | + build on top of. |
| 171 | +- The dep set in the regenerated `build.gradle` still has everything the wire |
| 172 | + tests need (today: nothing beyond what Fern emits, since the `testutil/` shim |
| 173 | + removed the `okhttp3.mockwebserver` requirement). |
| 174 | + |
| 175 | +## Don't |
| 176 | + |
| 177 | +- Don't edit `build.gradle` or `.fern/metadata.json` to add deps, change |
| 178 | + versions, or touch plugins/config. Both are Fern-managed; the change has to |
| 179 | + happen upstream. |
| 180 | +- Don't edit `README.md`. It's regenerated. Put project-specific docs in |
| 181 | + `reference.md` (which is in `.fernignore`). |
| 182 | +- Don't add backward-compatibility shims that mutate generated code. If the |
| 183 | + generated API changed in a way that breaks customers, absorb the change in the |
| 184 | + hand-maintained wrappers (`PipedreamClient*`) — that's what they exist for. |
| 185 | +- Don't introduce new test dependencies via `build.gradle`. Use the JDK or |
| 186 | + extend `testutil/`. |
| 187 | +- Don't try to add `static withCredentials(...)` / `static withToken(...)` on |
| 188 | + `PipedreamClient` — it won't compile (see the rationale section above). |
0 commit comments