{"_id":"@sholajegede/convex-cascading-deletes","_rev":"4-66f559d8fe906e16593cc87f90ae143d","name":"@sholajegede/convex-cascading-deletes","dist-tags":{"alpha":"0.1.1","latest":"0.1.2"},"versions":{"0.1.0":{"name":"@sholajegede/convex-cascading-deletes","version":"0.1.0","keywords":["convex","component","convex-component","cascading-deletes","cascade","database","relationships","referential-integrity","cleanup","delete"],"license":"Apache-2.0","_id":"@sholajegede/convex-cascading-deletes@0.1.0","maintainers":[{"name":"sholajegede","email":"jegedeshola@gmail.com"}],"homepage":"https://github.com/sholajegede/convex-cascading-deletes#readme","bugs":{"url":"https://github.com/sholajegede/convex-cascading-deletes/issues"},"dist":{"shasum":"499d19a59f6a58d26fe292021ba501583900d808","tarball":"https://registry.npmjs.org/@sholajegede/convex-cascading-deletes/-/convex-cascading-deletes-0.1.0.tgz","fileCount":58,"integrity":"sha512-C5ecXnBiK6EFDdPpsRChP/3lMWFhv/p8/imm+qq5thdM2AIPOGdpN2Mu55xhjl7zYBqSdUgEZFcDOtW2EsZ79w==","signatures":[{"sig":"MEUCIDzwrlCB2202JxGe6ZpDqQgSpwHFIOvsLvXDPBF12zyCAiEAuigDSqTQ8tMV0ys92RA1rDVJI2b1YdGUfmJKuTsVp14=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":88076},"type":"module","types":"./dist/client/index.d.ts","module":"./dist/client/index.js","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component":{"types":"./dist/component/_generated/component.d.ts"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"b2672dd230d315d9f7b67ebc6d13f92d856acf8b","scripts":{"all":"run-p -r 'dev:*' 'test:watch'","dev":"run-p -r 'dev:*'","lint":"eslint .","test":"vitest run --typecheck","alpha":"npm version prerelease --preid alpha && npm publish --tag alpha && git push --follow-tags","build":"tsc --project ./tsconfig.build.json","predev":"path-exists .env.local dist || (npm run build && convex dev --once)","release":"npm version patch && npm publish && git push --follow-tags","version":"vim -c 'normal o' -c 'normal o## '$npm_package_version CHANGELOG.md && prettier -w CHANGELOG.md && git add CHANGELOG.md","dev:build":"chokidar 'tsconfig*.json' 'src/**/*.ts' -i '**/*.test.ts' -c 'npm run build:codegen' --initial","typecheck":"tsc --noEmit && tsc -p example && tsc -p example/convex","preversion":"npm ci && npm run build:clean && run-p test lint typecheck","test:debug":"vitest --inspect-brk --no-file-parallelism","test:watch":"vitest --typecheck --clearScreen false","build:clean":"rm -rf dist *.tsbuildinfo && npm run build:codegen","dev:backend":"convex dev --typecheck-components","dev:frontend":"cd example && vite --clearScreen false","build:codegen":"npx convex codegen --component-dir ./src/component && npm run build","test:coverage":"vitest run --coverage --coverage.reporter=text","prepublishOnly":"npm whoami || npm login"},"_npmUser":{"name":"sholajegede","email":"jegedeshola@gmail.com"},"repository":{"url":"git+https://github.com/sholajegede/convex-cascading-deletes.git","type":"git"},"_npmVersion":"10.9.3","description":"A convex cascading deletes component for Convex.","directories":{},"_nodeVersion":"22.20.0","_hasShrinkwrap":false,"devDependencies":{"vite":"7.3.1","react":"^19.2.4","convex":"1.32.0","eslint":"9.39.3","vitest":"4.0.18","globals":"^17.4.0","prettier":"3.8.1","react-dom":"^19.2.4","@eslint/js":"9.39.4","pkg-pr-new":"^0.0.65","typescript":"5.9.3","@types/node":"^24.12.0","convex-test":"0.0.41","@types/react":"^19.2.14","chokidar-cli":"3.0.0","npm-run-all2":"8.0.4","path-exists-cli":"2.0.0","@edge-runtime/vm":"^5.0.0","@eslint/eslintrc":"^3.3.5","@types/react-dom":"^19.2.3","typescript-eslint":"8.56.1","eslint-plugin-react":"^7.37.5","@vitejs/plugin-react":"^5.1.4","@convex-dev/eslint-plugin":"^1.1.1","eslint-plugin-react-hooks":"^7.0.1","eslint-plugin-react-refresh":"^0.5.2"},"peerDependencies":{"react":"^18.3.1 || ^19.0.0","convex":"^1.32.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-cascading-deletes_0.1.0_1773418059150_0.7804992032094131","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@sholajegede/convex-cascading-deletes","version":"0.1.1","keywords":["convex","component","convex-component","cascading-deletes","cascade","database","relationships","referential-integrity","cleanup","delete"],"license":"Apache-2.0","_id":"@sholajegede/convex-cascading-deletes@0.1.1","maintainers":[{"name":"sholajegede","email":"jegedeshola@gmail.com"}],"homepage":"https://github.com/sholajegede/convex-cascading-deletes#readme","bugs":{"url":"https://github.com/sholajegede/convex-cascading-deletes/issues"},"dist":{"shasum":"d299eb7d7307b85c63e4f51ddcf780c75c3f586f","tarball":"https://registry.npmjs.org/@sholajegede/convex-cascading-deletes/-/convex-cascading-deletes-0.1.1.tgz","fileCount":58,"integrity":"sha512-sx0AwoQptXuXAouFTfDx3vUq9GY6WvyFPcBS5Ii4ZZU5Js9MNGC12H494V2dtItVJImGjuPX5iU4zgaM+PQlog==","signatures":[{"sig":"MEUCIHSrhTrgwfg3ca/GeWsKqsQ1Mb0Q+r4C5C8dm3DiZvDOAiEAjCbMRkmSuMByscCsthPHtg8Mft6eNQp7nqgaD4SoLRg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":78841},"type":"module","types":"./dist/client/index.d.ts","module":"./dist/client/index.js","exports":{".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./react":{"types":"./dist/react/index.d.ts","default":"./dist/react/index.js"},"./package.json":"./package.json","./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./_generated/component":{"types":"./dist/component/_generated/component.d.ts"},"./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts"}},"gitHead":"08b6de8a6293a7f21fc095535d0e4d5e33c0c7e4","scripts":{"all":"run-p -r 'dev:*' 'test:watch'","dev":"run-p -r 'dev:*'","lint":"eslint .","test":"vitest run --typecheck","alpha":"npm version prerelease --preid alpha && npm publish --tag alpha && git push --follow-tags","build":"tsc --project ./tsconfig.build.json","predev":"path-exists .env.local dist || (npm run build && convex dev --once)","release":"npm version patch && npm publish && git push --follow-tags","version":"vim -c 'normal o' -c 'normal o## '$npm_package_version CHANGELOG.md && prettier -w CHANGELOG.md && git add CHANGELOG.md","dev:build":"chokidar 'tsconfig*.json' 'src/**/*.ts' -i '**/*.test.ts' -c 'npm run build:codegen' --initial","typecheck":"tsc --noEmit && tsc -p example && tsc -p example/convex","preversion":"npm ci && npm run build:clean && run-p test lint typecheck","test:debug":"vitest --inspect-brk --no-file-parallelism","test:watch":"vitest --typecheck --clearScreen false","build:clean":"rm -rf dist *.tsbuildinfo && npm run build:codegen","dev:backend":"convex dev --typecheck-components","dev:frontend":"cd example && vite --clearScreen false","build:codegen":"npx convex codegen --component-dir ./src/component && npm run build","test:coverage":"vitest run --coverage --coverage.reporter=text","prepublishOnly":"npm whoami || npm login"},"_npmUser":{"name":"sholajegede","email":"jegedeshola@gmail.com"},"repository":{"url":"git+https://github.com/sholajegede/convex-cascading-deletes.git","type":"git"},"_npmVersion":"10.9.3","description":"A convex cascading deletes component for Convex.","directories":{},"_nodeVersion":"22.20.0","_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"vite":"7.3.1","react":"^19.2.4","convex":"1.32.0","eslint":"9.39.3","vitest":"4.0.18","globals":"^17.4.0","prettier":"3.8.1","react-dom":"^19.2.4","@eslint/js":"9.39.4","pkg-pr-new":"^0.0.65","typescript":"5.9.3","@types/node":"^24.12.0","convex-test":"0.0.41","@types/react":"^19.2.14","chokidar-cli":"3.0.0","npm-run-all2":"8.0.4","path-exists-cli":"2.0.0","@edge-runtime/vm":"^5.0.0","@eslint/eslintrc":"^3.3.5","@types/react-dom":"^19.2.3","typescript-eslint":"8.56.1","eslint-plugin-react":"^7.37.5","@vitejs/plugin-react":"^5.1.4","@convex-dev/eslint-plugin":"^1.1.1","eslint-plugin-react-hooks":"^7.0.1","eslint-plugin-react-refresh":"^0.5.2"},"peerDependencies":{"react":"^18.3.1 || ^19.0.0","convex":"^1.32.0"},"_npmOperationalInternal":{"tmp":"tmp/convex-cascading-deletes_0.1.1_1773423980466_0.7158069972830259","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@sholajegede/convex-cascading-deletes","description":"A convex cascading deletes component for Convex.","repository":{"type":"git","url":"git+https://github.com/sholajegede/convex-cascading-deletes.git"},"homepage":"https://github.com/sholajegede/convex-cascading-deletes#readme","bugs":{"url":"https://github.com/sholajegede/convex-cascading-deletes/issues"},"version":"0.1.2","license":"Apache-2.0","keywords":["convex","component","convex-component","cascading-deletes","cascade","database","relationships","referential-integrity","cleanup","delete"],"type":"module","scripts":{"dev":"run-p -r 'dev:*'","dev:backend":"convex dev --typecheck-components","dev:frontend":"cd example && vite --clearScreen false","dev:build":"chokidar 'tsconfig*.json' 'src/**/*.ts' -i '**/*.test.ts' -c 'npm run build:codegen' --initial","predev":"path-exists .env.local dist || (npm run build && convex dev --once)","build":"tsc --project ./tsconfig.build.json","build:codegen":"npx convex codegen --component-dir ./src/component && npm run build","build:clean":"rm -rf dist *.tsbuildinfo && npm run build:codegen","typecheck":"tsc --noEmit && tsc -p example && tsc -p example/convex","lint":"eslint .","all":"run-p -r 'dev:*' 'test:watch'","test":"vitest run --typecheck","test:watch":"vitest --typecheck --clearScreen false","test:debug":"vitest --inspect-brk --no-file-parallelism","test:coverage":"vitest run --coverage --coverage.reporter=text","preversion":"npm ci && npm run build:clean && run-p test lint typecheck","prepublishOnly":"npm whoami || npm login","alpha":"npm version prerelease --preid alpha && npm publish --tag alpha && git push --follow-tags","release":"npm version patch && npm publish && git push --follow-tags","version":"vim -c 'normal o' -c 'normal o## '$npm_package_version CHANGELOG.md && prettier -w CHANGELOG.md && git add CHANGELOG.md"},"exports":{"./package.json":"./package.json",".":{"types":"./dist/client/index.d.ts","default":"./dist/client/index.js"},"./test":"./src/test.ts","./_generated/component.js":{"types":"./dist/component/_generated/component.d.ts","default":"./dist/component/_generated/component.js"},"./_generated/component":{"types":"./dist/component/_generated/component.d.ts","default":"./dist/component/_generated/component.js"},"./convex.config.js":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"},"./convex.config":{"types":"./dist/component/convex.config.d.ts","default":"./dist/component/convex.config.js"}},"peerDependencies":{"convex":"^1.32.0","react":"^18.3.1 || ^19.0.0"},"devDependencies":{"@convex-dev/eslint-plugin":"^1.1.1","@edge-runtime/vm":"^5.0.0","@eslint/eslintrc":"^3.3.5","@eslint/js":"9.39.4","@types/node":"^24.12.0","@types/react":"^19.2.14","@types/react-dom":"^19.2.3","@vitejs/plugin-react":"^5.1.4","chokidar-cli":"3.0.0","convex":"1.32.0","convex-test":"0.0.41","eslint":"9.39.3","eslint-plugin-react":"^7.37.5","eslint-plugin-react-hooks":"^7.0.1","eslint-plugin-react-refresh":"^0.5.2","globals":"^17.4.0","npm-run-all2":"8.0.4","path-exists-cli":"2.0.0","pkg-pr-new":"^0.0.65","prettier":"3.8.1","react":"^19.2.4","react-dom":"^19.2.4","typescript":"5.9.3","typescript-eslint":"8.56.1","vite":"7.3.1","vitest":"4.0.18"},"types":"./dist/client/index.d.ts","module":"./dist/client/index.js","_id":"@sholajegede/convex-cascading-deletes@0.1.2","gitHead":"2f5d5b3825b91a90471e6ceed3c798863cdc9217","_nodeVersion":"22.20.0","_npmVersion":"10.9.3","dist":{"integrity":"sha512-utP2NvdWJsaQH/BgxOYDJhkbCaHdDMPeMvRUMcIV85A0fuKZSPZTdrKsOY5dfAMFZfuH7j9hRgNw1fI+MQybPA==","shasum":"453e7839e88a321d2ff50048fa3a91827b299322","tarball":"https://registry.npmjs.org/@sholajegede/convex-cascading-deletes/-/convex-cascading-deletes-0.1.2.tgz","fileCount":53,"unpackedSize":75277,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIGo/E+r4gwfLeh1OLt0VYVoQD3IA/RRaxPgsZPqJMnBWAiBBfd+7rP9Olj+1z5ai7l3/cys57uODRYI7jb24nFd03w=="}]},"_npmUser":{"name":"sholajegede","email":"jegedeshola@gmail.com"},"directories":{},"maintainers":[{"name":"sholajegede","email":"jegedeshola@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/convex-cascading-deletes_0.1.2_1773425497444_0.7743040584711889"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-13T16:07:39.064Z","modified":"2026-03-13T18:11:37.735Z","0.1.0":"2026-03-13T16:07:39.308Z","0.1.1":"2026-03-13T17:46:20.641Z","0.1.2":"2026-03-13T18:11:37.629Z"},"bugs":{"url":"https://github.com/sholajegede/convex-cascading-deletes/issues"},"license":"Apache-2.0","homepage":"https://github.com/sholajegede/convex-cascading-deletes#readme","keywords":["convex","component","convex-component","cascading-deletes","cascade","database","relationships","referential-integrity","cleanup","delete"],"repository":{"type":"git","url":"git+https://github.com/sholajegede/convex-cascading-deletes.git"},"description":"A convex cascading deletes component for Convex.","maintainers":[{"name":"sholajegede","email":"jegedeshola@gmail.com"}],"readme":"# @sholajegede/convex-cascading-deletes\n\nA [Convex component](https://www.convex.dev/components) for managing cascading deletes across related documents. Configure relationships via your existing indexes — when you delete a record, all dependent records are cleaned up automatically.\n\n[![npm version](https://badge.fury.io/js/@sholajegede%2Fconvex-cascading-deletes.svg)](https://badge.fury.io/js/@sholajegede%2Fconvex-cascading-deletes)\n[![Convex Component](https://www.convex.dev/components/badge/sholajegede/convex-cascading-deletes)](https://www.convex.dev/components/sholajegede/convex-cascading-deletes)\n\nFound a bug? Feature request? [File it here](https://github.com/sholajegede/convex-cascading-deletes/issues).\n\n<!-- START: Include on https://convex.dev/components -->\n\n## Features\n\n- **Cascading deletes** — delete a record and all its dependents in one call\n- **Relationship config** — declare relationships once using your existing indexes\n- **Scheduler-based batching** — large deletion trees are processed in batches via the Convex scheduler; deletes within a batch are atomic\n- **Circular relationship protection** — visited tracking prevents infinite loops\n- **Safe db helper** — patch `ctx.db` to throw on direct `.delete()` calls, enforcing cascade-only deletions\n- **Deletion logs** — every cascade is logged with a per-table count, queryable reactively\n- **Index validation** — validate your relationship config against the deployment at startup\n\n## Installation\n```sh\nnpm install @sholajegede/convex-cascading-deletes\n```\n\nAdd the component to your `convex/convex.config.ts`:\n```ts\nimport { defineApp } from \"convex/server\";\nimport convexCascadingDeletes from \"@sholajegede/convex-cascading-deletes/convex.config.js\";\n\nconst app = defineApp();\napp.use(convexCascadingDeletes);\n\nexport default app;\n```\n\n## Usage\n\nInstantiate the client once, declaring your table relationships:\n```ts\n// convex/cascadeDeletes.ts\nimport { components } from \"./_generated/api.js\";\nimport { CascadingDeletes } from \"@sholajegede/convex-cascading-deletes\";\n\nexport const cascadingDeletes = new CascadingDeletes(components.convexCascadingDeletes, {\n  relationships: [\n    {\n      sourceTable: \"posts\",      // child table\n      targetTable: \"users\",      // parent table\n      indexName: \"by_user\",      // index on posts that references users\n      fieldName: \"userId\",       // field on posts that holds the user ID\n    },\n    {\n      sourceTable: \"comments\",\n      targetTable: \"posts\",\n      indexName: \"by_post\",\n      fieldName: \"postId\",\n    },\n  ],\n});\n```\n\n### Delete with cascade\n```ts\n// convex/users.ts\nimport { action } from \"./_generated/server.js\";\nimport { cascadingDeletes } from \"./cascadeDeletes.js\";\nimport { v } from \"convex/values\";\n\nexport const deleteUser = action({\n  args: { userId: v.string() },\n  handler: async (ctx, args) => {\n    const counts = await cascadingDeletes.deleteWithCascade(ctx, {\n      table: \"users\",\n      id: args.userId,\n    });\n    // counts: { users: 1, posts: 4, comments: 12 }\n    return counts;\n  },\n});\n```\n\nDeleting a user cascades to their posts, then to each post's comments — all automatically, in the correct order.\n\n### Enforce cascade-only deletions\n```ts\nexport const updatePost = mutation({\n  args: { postId: v.id(\"posts\"), title: v.string() },\n  handler: async (ctx, args) => {\n    const db = cascadingDeletes.getSafeDb(ctx);\n    // db.delete() now throws — use deleteWithCascade instead\n    await db.patch(args.postId, { title: args.title }); // fine\n  },\n});\n```\n\n### Query deletion logs\n```ts\n// convex/logs.ts\nimport { query } from \"./_generated/server.js\";\nimport { components } from \"./_generated/api.js\";\nimport { v } from \"convex/values\";\n\nexport const getDeletionLog = query({\n  args: { table: v.string(), id: v.string() },\n  handler: async (ctx, args) => {\n    return await ctx.runQuery(components.convexCascadingDeletes.lib.getDeletionLog, {\n      rootTable: args.table,\n      rootId: args.id,\n    });\n  },\n});\n```\n```tsx\n// React — subscribes reactively\nconst log = useQuery(api.logs.getDeletionLog, { table: \"users\", id: userId });\n// log.deletedCounts — JSON string: { \"users\": 1, \"posts\": 4, \"comments\": 12 }\n// log.deletedAt     — timestamp of when the cascade ran\n```\n\n### Validate indexes at startup\n```ts\nexport const onStartup = mutation({\n  handler: async (ctx) => {\n    await cascadingDeletes.validate(ctx);\n  },\n});\n```\n\n## API\n\n### `CascadingDeletes` class\n\n| Method | Description |\n|--------|-------------|\n| `deleteWithCascade(ctx, { table, id })` | Delete a record and all configured dependents. Returns `{ [table]: count }`. |\n| `getDeletionLog(ctx, { table, id })` | Query the deletion log for a previously deleted record. |\n| `getSafeDb(ctx)` | Returns a patched `ctx.db` that throws on `.delete()`. |\n| `validate(ctx)` | Validates all configured relationships against the deployment. |\n\n### Relationship config\n\n| Field | Description |\n|-------|-------------|\n| `sourceTable` | The child table (holds the foreign key) |\n| `targetTable` | The parent table (the one being deleted) |\n| `indexName` | Index on `sourceTable` that references `targetTable` |\n| `fieldName` | Field on `sourceTable` that holds the parent ID |\n\n<!-- END: Include on https://convex.dev/components -->\n\n## Development\n```sh\nnpm i\nnpm run dev\n```\n\n## License\n\nApache-2.0","readmeFilename":"README.md"}