Skip to content

chore: dependency diet — remove 10 dead/duplicated deps and 800 lines of vendored code#1410

Open
UditDewan wants to merge 1 commit into
jitsucom:newjitsufrom
UditDewan:chore/dependency-diet
Open

chore: dependency diet — remove 10 dead/duplicated deps and 800 lines of vendored code#1410
UditDewan wants to merge 1 commit into
jitsucom:newjitsufrom
UditDewan:chore/dependency-diet

Conversation

@UditDewan

Copy link
Copy Markdown

Dependency diet: remove 10 dead/duplicated deps and 800 lines of vendored code

TL;DR

Metric Value
Lines removed 895 (82 added, net −813 incl. lockfile)
Dependencies removed 10 (webapps/console ×10 lines, cli/jitsu-cli ×1 — cuid counted once)
Files touched 17
Behavior changes intended None (three documented "value changes once" notes below)
Verification tsc --noEmit clean on console, rotor, jitsu-cli; juava builds; prettier clean; runnable smoke checks pass

This PR came out of a whole-repo over-engineering audit. Every removal below was verified by grepping all workspaces (webapps, libs, services, cli) for import sites before touching anything, and every replacement was re-typechecked afterwards.


1. Dead dependencies — deleted outright (zero call sites)

Dependency Version Evidence Notes
google-ads-api ^17.1.0-rest-beta 0 imports repo-wide Heavy package (pulls Google proto stubs). Likely a leftover from a removed integration.
recharts ^2.10.4 0 imports repo-wide chart.js is the charting library actually used (BillingDetails, EventStatPage). Two chart libs were installed; one was never imported.
stopwatch-node ^1.1.0 0 imports repo-wide juava already ships its own stopwatch.ts, which is what the code actually uses.
@types/pg-promise ^5.4.3 pg-promise itself is not a dependency and is never imported The real drivers are pg + pg-cursor. This was a types stub for a library that isn't installed.

Risk: none. Nothing imports these; removal only shrinks node_modules and the lockfile.

2. cuid → juava randomId() — 3 call sites

  • webapps/console/components/ConfigObjectEditor/ConfigEditor.tsx (new config object ids, ×2)
  • webapps/console/pages/[workspaceId]/settings/domains.tsx (new domain ids)
  • cli/jitsu-cli/src/commands/deploy.ts (new function ids on deploy)

Why: cuid is deprecated by its author (recommends cuid2). The repo already has its own id generator — randomId() from juava — used everywhere else for the same purpose (including config-objects-service.ts, which creates the same kinds of objects server-side). One id scheme is enough.

Behavior note: newly created objects get 24-char alphanumeric ids (juava format) instead of 25-char c… cuid format. Existing ids are untouched and never re-validated: a repo-wide grep found no format validation anywhere (ids are opaque strings in Prisma text columns). jitsu-cli already depends on juava (workspace:*), so no new dependency edge was created.

3. use-debounce → 6-line useDebouncedValue hook

Both call sites (AuditLog.tsx, workspaces.tsx) used exactly one shape: const [v] = useDebounce(value, ms) — debouncing a value, not a callback. That is a setTimeout/clearTimeout pair in an effect:

export function useDebouncedValue<T>(value: T, ms: number): T {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), ms);
    return () => clearTimeout(timer);
  }, [value, ms]);
  return debounced;
}

Added to webapps/console/lib/ui.tsx next to the other shared hooks. Identical trailing-edge semantics for this usage; none of use-debounce's extra options (leading, maxWait, equality fn) were used.

4. react-use → 3-line useTitle

The entire react-use dependency existed for one hook, useTitle, imported in one file (lib/ui.tsx) and re-exported. All six call sites pass a plain string and none use restoreOnUnmount. Replaced with:

export function useTitle(title: string) {
  useEffect(() => {
    document.title = title;
  }, [title]);
}

5. @loadable/componentnext/dynamic

One call site, DataView/JSONView.tsx, lazy-loading react-json-view. This is a Next.js app; next/dynamic is the framework-native way to do exactly this, including skipping SSR (react-json-view is browser-only):

const ReactJson = dynamic(() => import("react-json-view"), { ssr: false });

The old code wrapped the import in a hand-built new Promise((r, c) => …) to unwrap .defaultnext/dynamic does that unwrapping itself.

6. timezones-listIntl.supportedValuesOf("timeZone")

One call site: the sync scheduler timezone <Select> in SyncEditorPage.tsx. The platform ships the timezone database; a static npm copy of it goes stale. Replacement builds the same { value, label } shape with a native GMT-offset label:

const timezones = Intl.supportedValuesOf("timeZone").map(tz => {
  const offset = new Intl.DateTimeFormat("en-US", { timeZone: tz, timeZoneName: "longOffset" })
    .formatToParts(new Date())
    .find(p => p.type === "timeZoneName")!.value;
  return { value: tz, label: `${tz} (${offset})` };
});

Behavior notes, checked explicitly:

  • The mapper was smoke-tested over all 418 zones returned by Node's ICU — no entry lacks a timeZoneName part, every label matches /GMT([+-]\d{2}:\d{2})?\)$/. Module-level evaluation cannot throw.
  • ICU may return legacy canonical aliases (Asia/Calcutta vs Asia/Kolkata) depending on engine/version. Every returned name is a valid IANA identifier, which is all the scheduler backend (cron-parser) requires.
  • Saved syncs whose stored tz string isn't in the new list still render fine — antd Select shows the raw value when no option matches. Etc/UTC (not in the ICU list) remains explicitly prepended as the default option, unchanged.

7. object-hashjuavaHash("md5", stableHash(...))

Two files imported object-hash; one of them (pages/api/admin/export/[name]/index.ts) imported both object-hash and stable-hash and — at line 916 — already used the exact idiom this PR standardizes on: juavaHash("md5", stableHash(x)). Now the whole file uses one hashing scheme via a 1-line local hash helper; tasks.tsx (a React.memo prop comparator, purely ephemeral) uses stableHash equality directly.

Behavior note — hash values change once, verified safe: the optionsHash / credentialsHash / codeHash fields emitted by the admin export API change value on the first deploy (sha1-of-object-hash → md5-of-stable-hash). Traced every downstream consumer before making this change:

  • services/rotor/src/lib/warehouse-store.ts — key of an in-memory singleton map (${con.id}-${con.credentialsHash})
  • services/rotor/src/lib/message-handler.ts, functions-server.ts — in-memory UDF cache keys
  • bulker/operator/types.go — passthrough struct fields, never compared against stored values

All are cache keys that already miss on every process restart. Effect of the switch: one extra cache refresh on the first deploy, then steady state. Nothing persists or replays old hash values. Important: stable-hash output alone is a serialization (would leak credentials in credentialsHash) — that's why it is always wrapped in juavaHash("md5", …), never used raw for these fields.

8. juava debounce.ts (130 lines) → 4-line resettable timer

libs/juava/src/debounce.ts was a vendored copy of lodash's debounce, not exported from the package index, with exactly one internal consumer: singleton.ts used it as a resettable TTL timer (every singleton access postpones cleanup to ttlSec after the last access). That is:

let cleanupTimer: ReturnType<typeof setTimeout> | undefined;
const scheduleCleanup = () => {
  clearTimeout(cleanupTimer);
  cleanupTimer = setTimeout(() => clearSingleton(globalName, opts.cleanupFunc), 1000 * opts.ttlSec!);
};

Trailing-edge semantics preserved exactly (verified: debounceCleanup is only ever called.cancel()/.flush()/.pending() were never used). The 130-line file is deleted.

9. juava deepCopystructuredClone one-liner

deepCopy had exactly one real consumer: config-objects-service.ts:283, copying a Prisma JSON config column (JSON-safe by construction). structuredClone is native in Node 17+ and all browsers since 2022.

Semantics documented at the definition: structuredClone throws on functions in the tree (the old copy silently copied them by reference) and correctly clones Dates (the old copy mangled them into {} — arguably a latent bug). Callers must pass JSON-safe data; the one existing caller does.

Deliberately NOT deduplicated: services/rotor/src/lib/udf-chain.ts keeps its own local deepCopy. Its file header forbids importing juava — the module is bundled into a permission-less Deno sandbox worker, and juava's index pulls in process, logging, and ClickHouse client code. A ponytail: comment now records why the duplicate is intentional. Same for libs/jitsu-js/src/analytics-plugin.ts (standalone browser SDK, own copy, untouched).


Verification

Check Result
pnpm --filter @jitsu-internal/console run typecheck ✅ clean (run twice — after code edits and after final juava changes)
pnpm --filter rotor run typecheck ✅ clean
pnpm --filter jitsu-cli run typecheck ✅ clean
pnpm --filter juava run build ✅ clean
pnpm install ✅ lockfile updated; only pre-existing peer warnings remain
Prettier ✅ all touched files pass --check
Grep for lingering imports of all 10 removed packages ✅ zero matches across webapps, libs, services, cli
Runnable smoke check (node) structuredClone parity vs old deepCopy on nested data; timezone mapper over all 418 ICU zones; resettable-timer debounce semantics (3 rapid calls → 1 firing)

Explicitly out of scope (audited, deliberately skipped)

  1. mjml / mjml-react → @react-email consolidation. Two email templating stacks coexist (lib/server/templates.tsx on mjml, emails/ on react-email). Cutting mjml is a real template migration touching production email rendering — separate PR.
  2. libs/jsondiffpatch vendored fork (654 lines). Consumed by jitsu-js and rotor. Replacing it with upstream jsondiffpatch@0.6.0 requires diffing the fork against upstream first to rule out local patches.
  3. juava isEqual / collections.ts → lodash. juava does not currently depend on lodash; adding a dependency edge to delete 26 lines is a net loss.
  4. firebase / @types/cookie / other low-confidence candidates — not conclusively dead, left alone.

Rollback

Single commit, no data migrations, no persisted-format changes (new-id format and hash values are forward-only but non-breaking, as analyzed above). git revert restores everything, including the deleted debounce.ts.

Deletes dependencies that were unused, deprecated, or duplicating
functionality already present in the stdlib, the platform, or another
installed dependency. Net -813 lines. No functional changes intended;
see PR description for the full per-change risk analysis.

- delete unused deps: google-ads-api, recharts, stopwatch-node, @types/pg-promise
- cuid -> juava randomId() (cuid is deprecated upstream)
- use-debounce -> local 6-line useDebouncedValue hook
- react-use -> local 3-line useTitle hook
- @loadable/component -> next/dynamic
- timezones-list -> Intl.supportedValuesOf("timeZone")
- object-hash -> juavaHash("md5", stableHash(...)) (existing in-repo idiom)
- delete juava/src/debounce.ts (130-line vendored lodash debounce) ->
  4-line resettable setTimeout in singleton.ts
- juava deepCopy -> structuredClone one-liner
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant