From cc01fb941874bcef4e8cffb9de4f91bd4ca20538 Mon Sep 17 00:00:00 2001 From: Amruth Pillai Date: Sun, 8 Feb 2026 00:16:11 +0100 Subject: [PATCH] Feature: Implement Atomic Resume Patching API (#2692) --- docs/docs.json | 7 +- docs/guides/using-the-patch-api.mdx | 191 ++++++++++++ package.json | 23 +- pnpm-lock.yaml | 373 +++++++++++------------ src/integrations/orpc/dto/resume.ts | 20 ++ src/integrations/orpc/router/resume.ts | 26 ++ src/integrations/orpc/services/resume.ts | 50 +++ src/utils/resume/patch.ts | 109 +++++++ 8 files changed, 594 insertions(+), 205 deletions(-) create mode 100644 docs/guides/using-the-patch-api.mdx create mode 100644 src/utils/resume/patch.ts diff --git a/docs/docs.json b/docs/docs.json index 7aff53c44..2d4a526c9 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -69,7 +69,12 @@ }, { "group": "Integrations", - "pages": ["guides/using-the-api", "guides/using-ai", "guides/json-resume-schema"] + "pages": [ + "guides/using-the-api", + "guides/using-the-patch-api", + "guides/using-ai", + "guides/json-resume-schema" + ] }, { "group": "Self-Hosting", diff --git a/docs/guides/using-the-patch-api.mdx b/docs/guides/using-the-patch-api.mdx new file mode 100644 index 000000000..c22b5cf02 --- /dev/null +++ b/docs/guides/using-the-patch-api.mdx @@ -0,0 +1,191 @@ +--- +title: "Using the Patch API" +description: "Learn how to partially update your resume using JSON Patch (RFC 6902) operations" +--- + +The Patch API lets you make small, targeted changes to your resume without sending the entire data object. Instead of replacing the whole resume with a `PUT`, you send a list of **JSON Patch** operations that describe exactly what to change. + +This is based on the [JSON Patch (RFC 6902)](https://datatracker.ietf.org/doc/html/rfc6902) standard. + +## When to Use PATCH vs PUT + +| Use case | Method | +| --- | --- | +| Update a single field (e.g., name, headline) | **PATCH** | +| Add or remove an item in a section | **PATCH** | +| Change template, colors, or fonts | **PATCH** | +| Replace the entire resume data at once | **PUT** | + + + The PATCH endpoint only modifies the resume `data` (the JSONB column). To update top-level resume properties like `name`, `slug`, `tags`, or `isPublic`, use the existing `PUT /resume/{id}` endpoint. + + +## Authentication + +All requests require your API key in the `x-api-key` header. See [Using the API](/guides/using-the-api) for how to create one. + + + If you're self-hosting, replace `https://rxresu.me` with your instance URL. The API is served under `/api/openapi`. + + +## Endpoint + +``` +PATCH /api/openapi/resume/{id} +``` + +### Request Body + +The resume ID is taken from the URL path, so the request body only requires the `operations` array: + +```json +{ + "operations": [ + { "op": "replace", "path": "/basics/name", "value": "Jane Doe" } + ] +} +``` + +Each operation is an object with the following properties: + +| Property | Required | Description | +| --- | --- | --- | +| `op` | Yes | The operation to perform: `add`, `remove`, `replace`, `move`, `copy`, or `test` | +| `path` | Yes | A JSON Pointer (RFC 6901) to the target location in the resume data | +| `value` | For `add`, `replace`, `test` | The value to use for the operation | +| `from` | For `move`, `copy` | A JSON Pointer to the source location | + +## Examples + +### Replace a Basic Field + +Update the resume holder's name and headline: + +```bash +curl -X PATCH "https://rxresu.me/api/openapi/resume/YOUR_RESUME_ID" \ + -H "x-api-key: YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "operations": [ + { "op": "replace", "path": "/basics/name", "value": "Jane Doe" }, + { "op": "replace", "path": "/basics/headline", "value": "Senior Software Engineer" } + ] + }' +``` + +### Add an Experience Entry + +Append a new item to the experience section: + +```bash +curl -X PATCH "https://rxresu.me/api/openapi/resume/YOUR_RESUME_ID" \ + -H "x-api-key: YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "operations": [ + { + "op": "add", + "path": "/sections/experience/items/-", + "value": { + "id": "a1b2c3d4-0000-0000-0000-000000000000", + "hidden": false, + "company": "Acme Corp", + "position": "Staff Engineer", + "location": "San Francisco, CA", + "period": "Jan 2024 - Present", + "website": { "url": "https://acme.example.com", "label": "Acme Corp" }, + "description": "

Leading the platform team.

" + } + } + ] + }' +``` + + + The path `/sections/experience/items/-` uses the special `-` index, which means "append to the end of the array". + To insert at a specific position, use a numeric index like `/sections/experience/items/0` for the beginning. + + +### Remove an Item from a Section + +Remove the second skill (index `1`) from the skills section: + +```bash +curl -X PATCH "https://rxresu.me/api/openapi/resume/YOUR_RESUME_ID" \ + -H "x-api-key: YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "operations": [ + { "op": "remove", "path": "/sections/skills/items/1" } + ] + }' +``` + +### Update Metadata (Template, Colors, Fonts) + +Switch the template and update the primary color: + +```bash +curl -X PATCH "https://rxresu.me/api/openapi/resume/YOUR_RESUME_ID" \ + -H "x-api-key: YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "operations": [ + { "op": "replace", "path": "/metadata/template", "value": "bronzor" }, + { "op": "replace", "path": "/metadata/design/colors/primary", "value": "rgba(37, 99, 235, 1)" } + ] + }' +``` + +### Test-Then-Replace (Optimistic Concurrency) + +The `test` operation checks that a value matches before proceeding. If the test fails, the entire patch is rejected. This is useful to avoid overwriting changes made by another client: + +```bash +curl -X PATCH "https://rxresu.me/api/openapi/resume/YOUR_RESUME_ID" \ + -H "x-api-key: YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "operations": [ + { "op": "test", "path": "/basics/name", "value": "Albert Einstein" }, + { "op": "replace", "path": "/basics/name", "value": "Jane Doe" } + ] + }' +``` + +If `/basics/name` is not `"Albert Einstein"` at the time of the request, the entire patch will fail with a `400` error and no changes will be applied. + +### Move an Item Within a Section + +Move the first experience item to the third position: + +```bash +curl -X PATCH "https://rxresu.me/api/openapi/resume/YOUR_RESUME_ID" \ + -H "x-api-key: YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "operations": [ + { "op": "move", "from": "/sections/experience/items/0", "path": "/sections/experience/items/2" } + ] + }' +``` + +## Error Handling + +| Status | Error Code | Description | +| --- | --- | --- | +| `400` | `INVALID_PATCH_OPERATIONS` | The operations are structurally invalid, target a non-existent path, or produce resume data that fails schema validation. | +| `401` | `UNAUTHORIZED` | Missing or invalid API key. | +| `404` | `NOT_FOUND` | The resume does not exist or does not belong to the authenticated user. | +| `403` | `RESUME_LOCKED` | The resume is locked and cannot be modified. Unlock it first. | + + + All operations in a single request are applied atomically. If any operation fails (including a `test`), none of the operations are applied. + + +## Tips + +- **Fetch first, then patch.** Use `GET /resume/{id}` to inspect the current structure before crafting your operations. This helps you target the correct paths and array indices. +- **Use `test` for safety.** When updating a value you expect to be a specific value, combine `test` + `replace` to avoid accidentally overwriting concurrent changes. +- **Batch related changes.** You can send multiple operations in a single request. They are applied in order, so later operations can depend on earlier ones. +- **The `-` index appends.** When adding items to arrays, use `-` as the index (e.g., `/sections/skills/items/-`) to append to the end. diff --git a/package.json b/package.json index e1d93750f..9ff98f1bd 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "5.0.5", "license": "MIT", "type": "module", - "packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264", + "packageManager": "pnpm@10.29.1+sha512.48dae233635a645768a3028d19545cacc1688639eeb1f3734e42d6d6b971afbf22aa1ac9af52a173d9c3a20c15857cfa400f19994d79a2f626fcc73fccda9bbc", "repository": { "type": "git", "url": "https://github.com/amruthpillai/reactive-resume.git" @@ -35,8 +35,8 @@ "@ai-sdk/google": "^3.0.22", "@ai-sdk/openai": "^3.0.26", "@aws-sdk/client-s3": "^3.985.0", - "@better-auth/core": "1.5.0-beta.11", - "@better-auth/passkey": "1.5.0-beta.11", + "@better-auth/core": "1.5.0-beta.13", + "@better-auth/passkey": "1.5.0-beta.13", "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", @@ -56,20 +56,20 @@ "@sindresorhus/slugify": "^3.0.0", "@t3-oss/env-core": "^0.13.10", "@tanstack/react-query": "5.90.20", - "@tanstack/react-router": "^1.158.1", - "@tanstack/react-router-ssr-query": "^1.158.1", - "@tanstack/react-start": "^1.158.3", - "@tanstack/zod-adapter": "^1.158.1", + "@tanstack/react-router": "^1.158.4", + "@tanstack/react-router-ssr-query": "^1.158.4", + "@tanstack/react-start": "^1.159.0", + "@tanstack/zod-adapter": "^1.158.4", "@tiptap/extension-highlight": "^3.19.0", "@tiptap/extension-table": "^3.19.0", "@tiptap/extension-text-align": "^3.19.0", "@tiptap/pm": "^3.19.0", "@tiptap/react": "^3.19.0", "@tiptap/starter-kit": "^3.19.0", - "ai": "^6.0.75", - "ai-sdk-ollama": "^3.4.0", + "ai": "^6.0.77", + "ai-sdk-ollama": "^3.5.0", "bcrypt": "^6.0.0", - "better-auth": "1.5.0-beta.11", + "better-auth": "1.5.0-beta.13", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", @@ -78,13 +78,14 @@ "drizzle-zod": "^0.8.3", "es-toolkit": "^1.44.0", "fast-deep-equal": "^3.1.3", + "fast-json-patch": "^3.1.1", "fuse.js": "^7.1.0", "immer": "^11.1.3", "input-otp": "^1.4.2", "js-cookie": "^3.0.5", "monaco-editor": "^0.55.1", "motion": "^12.33.0", - "nodemailer": "^8.0.0", + "nodemailer": "^8.0.1", "pg": "^8.18.0", "puppeteer-core": "^24.37.2", "qrcode.react": "^4.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c256a580d..5ea5cd7f7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,11 +24,11 @@ importers: specifier: ^3.985.0 version: 3.985.0 '@better-auth/core': - specifier: 1.5.0-beta.11 - version: 1.5.0-beta.11(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) + specifier: 1.5.0-beta.13 + version: 1.5.0-beta.13(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.1(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) '@better-auth/passkey': - specifier: 1.5.0-beta.11 - version: 1.5.0-beta.11(38c18a8d8b45924e68e857ec7a702968) + specifier: 1.5.0-beta.13 + version: 1.5.0-beta.13(2258e4f412fd167666cad25c7546abd6) '@dnd-kit/core': specifier: ^6.3.1 version: 6.3.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -87,17 +87,17 @@ importers: specifier: 5.90.20 version: 5.90.20(react@19.2.4) '@tanstack/react-router': - specifier: ^1.158.1 - version: 1.158.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: ^1.158.4 + version: 1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/react-router-ssr-query': - specifier: ^1.158.1 - version: 1.158.1(@tanstack/query-core@5.90.20)(@tanstack/react-query@5.90.20(react@19.2.4))(@tanstack/react-router@1.158.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.158.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: ^1.158.4 + version: 1.158.4(@tanstack/query-core@5.90.20)(@tanstack/react-query@5.90.20(react@19.2.4))(@tanstack/react-router@1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.158.4)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/react-start': - specifier: ^1.158.3 - version: 1.158.3(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rolldown-vite@7.3.1(@types/node@25.2.1)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)) + specifier: ^1.159.0 + version: 1.159.0(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rolldown-vite@7.3.1(@types/node@25.2.1)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)) '@tanstack/zod-adapter': - specifier: ^1.158.1 - version: 1.158.1(@tanstack/react-router@1.158.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(zod@4.3.6) + specifier: ^1.158.4 + version: 1.158.4(@tanstack/react-router@1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(zod@4.3.6) '@tiptap/extension-highlight': specifier: ^3.19.0 version: 3.19.0(@tiptap/core@3.19.0(@tiptap/pm@3.19.0)) @@ -117,17 +117,17 @@ importers: specifier: ^3.19.0 version: 3.19.0 ai: - specifier: ^6.0.75 - version: 6.0.75(zod@4.3.6) + specifier: ^6.0.77 + version: 6.0.77(zod@4.3.6) ai-sdk-ollama: - specifier: ^3.4.0 - version: 3.4.0(ai@6.0.75(zod@4.3.6))(zod@4.3.6) + specifier: ^3.5.0 + version: 3.5.0(ai@6.0.77(zod@4.3.6))(zod@4.3.6) bcrypt: specifier: ^6.0.0 version: 6.0.0 better-auth: - specifier: 1.5.0-beta.11 - version: 1.5.0-beta.11(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@tanstack/react-start@1.158.3(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rolldown-vite@7.3.1(@types/node@25.2.1)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)))(drizzle-kit@1.0.0-beta.9-e89174b)(drizzle-orm@1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)))(mongodb@7.0.0(socks@2.8.7))(mysql2@3.15.3)(pg@8.18.0)(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: 1.5.0-beta.13 + version: 1.5.0-beta.13(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@tanstack/react-start@1.159.0(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rolldown-vite@7.3.1(@types/node@25.2.1)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)))(drizzle-kit@1.0.0-beta.9-e89174b)(drizzle-orm@1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)))(mongodb@7.0.0(socks@2.8.7))(mysql2@3.15.3)(pg@8.18.0)(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -152,6 +152,9 @@ importers: fast-deep-equal: specifier: ^3.1.3 version: 3.1.3 + fast-json-patch: + specifier: ^3.1.1 + version: 3.1.1 fuse.js: specifier: ^7.1.0 version: 7.1.0 @@ -171,8 +174,8 @@ importers: specifier: ^12.33.0 version: 12.33.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) nodemailer: - specifier: ^8.0.0 - version: 8.0.0 + specifier: ^8.0.1 + version: 8.0.1 pg: specifier: ^8.18.0 version: 8.18.0 @@ -336,8 +339,8 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/gateway@3.0.37': - resolution: {integrity: sha512-w7rxPd2WOuSr4he2ALvtCAM/W1/33rGdv3kkU52tCGj/bJSYJnJDpnTvoWRLNESp9HEkkiNFfBLFZY0PB9n4aQ==} + '@ai-sdk/gateway@3.0.39': + resolution: {integrity: sha512-SeCZBAdDNbWpVUXiYgOAqis22p5MEYfrjRw0hiBa5hM+7sDGYQpMinUjkM8kbPXMkY+AhKLrHleBl+SuqpzlgA==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -354,22 +357,12 @@ packages: peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/provider-utils@4.0.13': - resolution: {integrity: sha512-HHG72BN4d+OWTcq2NwTxOm/2qvk1duYsnhCDtsbYwn/h/4zeqURu1S0+Cn0nY2Ysq9a9HGKvrYuMn9bgFhR2Og==} - engines: {node: '>=18'} - peerDependencies: - zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/provider-utils@4.0.14': resolution: {integrity: sha512-7bzKd9lgiDeXM7O4U4nQ8iTxguAOkg8LZGD9AfDVZYjO5cKYRwBPwVjboFcVrxncRHu0tYxZtXZtiLKpG4pEng==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 - '@ai-sdk/provider@3.0.7': - resolution: {integrity: sha512-VkPLrutM6VdA924/mG8OS+5frbVTcu6e046D2bgDo00tehBANR1QBJ/mPcZ9tXMFOsVcm6SQArOregxePzTFPw==} - engines: {node: '>=18'} - '@ai-sdk/provider@3.0.8': resolution: {integrity: sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ==} engines: {node: '>=18'} @@ -1175,65 +1168,65 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} - '@better-auth/core@1.5.0-beta.11': - resolution: {integrity: sha512-8fbGKD8RhnKJ6FSUKDj4mHp1RIS82T4vB4AjFj95/XOEExzM9qShbYKFL5+RZrkt+oqSViuFX61iAB+LH/SJ9g==} + '@better-auth/core@1.5.0-beta.13': + resolution: {integrity: sha512-oOK1O3bwfzebK5W4iIYOdB+IBLk+RLWfOm/MU6dMq4l1HNWvi5iZ75lxxk7Sgx0MfaeEn1C+lXIHplDyeeZKhQ==} peerDependencies: '@better-auth/utils': 0.3.1 '@better-fetch/fetch': 1.1.21 - better-call: 1.2.0 + better-call: 1.2.1 jose: ^6.1.0 kysely: ^0.28.5 nanostores: ^1.0.1 - '@better-auth/drizzle-adapter@1.5.0-beta.11': - resolution: {integrity: sha512-XtxYPr3nNlIndONyoxd3jijkq9g1/p+x8WZd6ZDP8ygcz8iARS31CCJ919eH6P6rpoUy4VXx+eSPvjzALbKrWQ==} + '@better-auth/drizzle-adapter@1.5.0-beta.13': + resolution: {integrity: sha512-PZmyTDun2AEhwQtO/xQb8Zi5eBMyJ/o69T3h4wj59CxHtBD11fvhY19csJd3hVk1YvFJobgMv3icWXmz02Egmw==} peerDependencies: - '@better-auth/core': 1.5.0-beta.11 + '@better-auth/core': 1.5.0-beta.13 '@better-auth/utils': ^0.3.0 drizzle-orm: '>=0.41.0' - '@better-auth/kysely-adapter@1.5.0-beta.11': - resolution: {integrity: sha512-VV5RF0+648leeNgXoNbuoePh6PjmRyVP2qxgDU5gnEzA8x9SqfOUppHhi4ssfOdjaQHf+pLo1sWHFb8Wy7+JlQ==} + '@better-auth/kysely-adapter@1.5.0-beta.13': + resolution: {integrity: sha512-1Ek0jV/FiFUEcTkoPkf4MWNR3k6b5t1mLBanJVjvSD+UhAXhe93H8onEKlRcSopNu2ls6z70BI75jYBxYSdf5Q==} peerDependencies: - '@better-auth/core': 1.5.0-beta.11 + '@better-auth/core': 1.5.0-beta.13 '@better-auth/utils': ^0.3.0 kysely: ^0.27.0 || ^0.28.0 - '@better-auth/memory-adapter@1.5.0-beta.11': - resolution: {integrity: sha512-Ggq+F2YB2ywTfX/0w5xgFvr73IiihmVF6IUYM0v79BMnEQa72Qc3J/8D/iU33WRNwvYAo4CfDD1OECp/78PjgA==} + '@better-auth/memory-adapter@1.5.0-beta.13': + resolution: {integrity: sha512-7pCJXEiI4MPduxueVtUUnfUOxOQhu4EcFmQQ7zUodumtYmi5Xwar/bctl7v8GsabkG8bZwEQUEvO5uqVtnGwhA==} peerDependencies: - '@better-auth/core': 1.5.0-beta.11 + '@better-auth/core': 1.5.0-beta.13 '@better-auth/utils': ^0.3.0 - '@better-auth/mongo-adapter@1.5.0-beta.11': - resolution: {integrity: sha512-32JQTtp/84tZd0ZpWSV4+uaiMhyZGmeyAJF0B4FrHnI8/i/auOrNK3imLP+afv/ne7U+hQEyfUObgtUDr+DLzw==} + '@better-auth/mongo-adapter@1.5.0-beta.13': + resolution: {integrity: sha512-FDqbLWqreELN/wiIwGlItZmi+FShzGPNdNk2KaRo2hVLakBQy9hlCtnxeYV7OfptOPs+AkGc8hboKjBzFA1uUQ==} peerDependencies: - '@better-auth/core': 1.5.0-beta.11 + '@better-auth/core': 1.5.0-beta.13 '@better-auth/utils': ^0.3.0 mongodb: ^6.0.0 || ^7.0.0 - '@better-auth/passkey@1.5.0-beta.11': - resolution: {integrity: sha512-dZ2oO4ePY9mo0h6SO3Uzhh/yMuD0rYKVosDzD6kuwyX+0S0guWbpSL+r1VwdZzOT/eP3XIMv6AnA+J4gmD7ibQ==} + '@better-auth/passkey@1.5.0-beta.13': + resolution: {integrity: sha512-M6ORIcQQUIkmTDKZ4lvWn4grYisuE48k995Bdv4DoBW8ezbkHoJt0tRzxSJoD6BsArV79jZ+jG9sci1tfeF6rw==} peerDependencies: - '@better-auth/core': 1.5.0-beta.11 + '@better-auth/core': 1.5.0-beta.13 '@better-auth/utils': 0.3.1 '@better-fetch/fetch': 1.1.21 - better-auth: 1.5.0-beta.11 - better-call: 1.2.0 + better-auth: 1.5.0-beta.13 + better-call: 1.2.1 nanostores: ^1.0.1 - '@better-auth/prisma-adapter@1.5.0-beta.11': - resolution: {integrity: sha512-jAlAql0bZx62IgZ6/UsXXCR6L28tKipLYe4iPutjMAOi9Wl9oM2OsAmr/ZhcrHj9mjmKZXot1mqGcCcROZ1aSg==} + '@better-auth/prisma-adapter@1.5.0-beta.13': + resolution: {integrity: sha512-w2+KMfoWrPeYKAmG1tinwmsHe2Lc3HNyOASEO5jw6oJY7BrLDKcqeqCJuhavuwhd2fCAq3fEiqyJVCYDDLVKqA==} peerDependencies: - '@better-auth/core': 1.5.0-beta.11 + '@better-auth/core': 1.5.0-beta.13 '@better-auth/utils': ^0.3.0 '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 prisma: ^5.0.0 || ^6.0.0 || ^7.0.0 - '@better-auth/telemetry@1.5.0-beta.11': - resolution: {integrity: sha512-DDHXD37FCF+N7Sx80wY1QI351SgCMH7KQ2YsxwF5JDw/DYdY4vbjcEGYB7Ipum+dzEfq5o6cSdWvQqtjcT2o2w==} + '@better-auth/telemetry@1.5.0-beta.13': + resolution: {integrity: sha512-Ju/29VMM+pfgFs/i7rX7scMO2QFgNbt/PRQGU3VJEQDC9M9NHhGgLe2kPkUEFQic5Z6sOYpXTGKFJyF/YSJRFw==} peerDependencies: - '@better-auth/core': 1.5.0-beta.11 + '@better-auth/core': 1.5.0-beta.13 '@better-auth/utils@0.3.1': resolution: {integrity: sha512-+CGp4UmZSUrHHnpHhLPYu6cV+wSUSvVbZbNykxhUDocpVNTo9uFFxw/NqJlh1iC4wQ9HKKWGCKuZ5wUgS0v6Kg==} @@ -3899,8 +3892,8 @@ packages: peerDependencies: react: ^18 || ^19 - '@tanstack/react-router-ssr-query@1.158.1': - resolution: {integrity: sha512-WDwwwVFjoDsm9IMjbv+cQJzCkUZ1zfZYy/bXqjcNmN+eK7NLIc3ePCOeMjqb3bMOg5173qPMyFD9mWeHg+wNjQ==} + '@tanstack/react-router-ssr-query@1.158.4': + resolution: {integrity: sha512-f+XzxO06ILM2i5CGtWqcb3+yaAvp8XgT5hMykKmwwaBnf3Ctc6O8tN/05Ovj0ajXWuROk3HTjg67OcWD7JxI6Q==} engines: {node: '>=12'} peerDependencies: '@tanstack/query-core': '>=5.90.0' @@ -3909,29 +3902,29 @@ packages: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-router@1.158.1': - resolution: {integrity: sha512-ZRBhs0tJDPeYGVrBhXPkGs+mOKqKKMM4OfvYSNvWIYZGfs8KQcqxPaN8OnUvKsnAGtzwusVWDpBipqVZWJd0lA==} + '@tanstack/react-router@1.158.4': + resolution: {integrity: sha512-i15xXumgvpuM+4NSuIwgouGezuj9eHjZsgpTZSQ7E9pa8rYmhZbWnf8xU68qaLmaKIol/e75o/YzVH2QWHs3iQ==} engines: {node: '>=12'} peerDependencies: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-start-client@1.158.3': - resolution: {integrity: sha512-ScZwp1ovnthjroqs6qVk37AIbN+9Bf5MDK+uFhU+T6fWew/XHrxgtJzx2k1vqXJBi0tUKqZYSaH2zqc3OIdkfg==} + '@tanstack/react-start-client@1.158.4': + resolution: {integrity: sha512-ctEBgpYAPZ3i4EPZlJ45XS/lXPO73MkELec+hXf8NfK0lDQDaUy7LfWu41NPaftdZFJPOncDCfutwpUXD98YlA==} engines: {node: '>=22.12.0'} peerDependencies: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-start-server@1.158.3': - resolution: {integrity: sha512-luLTjbccYPSl1nvVhP2vJGgIVPLksvXap2oXCGGouwG+2nGPprBEglLAtd9gmnJ7dbd4QknSIXBFoeUuoCsdsQ==} + '@tanstack/react-start-server@1.159.0': + resolution: {integrity: sha512-1nPj7TEOpoIlTW0lftaHuU9Ol1ZDQwRCUWr6UvaPUbapq9nWR8kwYFjyCLbopBjyakFFNgz88/stdbZObt5h2A==} engines: {node: '>=22.12.0'} peerDependencies: react: '>=18.0.0 || >=19.0.0' react-dom: '>=18.0.0 || >=19.0.0' - '@tanstack/react-start@1.158.3': - resolution: {integrity: sha512-dn54I+frxfItkxAQngBZDwlnOM1DM53Lhd+iHhinRRk1HzwoGXtc1lJZ/aIk5YanXguIFsxBhqsejX3+kT4y8A==} + '@tanstack/react-start@1.159.0': + resolution: {integrity: sha512-/ky8Pbu0cmj5dAQfi8LXHpAd/eepyQqDo0eSI/OPYQ2wZ8u8UPwycFvou8t8mq5pkinu+l7JX45UD7mNvzvVNg==} engines: {node: '>=22.12.0'} peerDependencies: react: '>=18.0.0 || >=19.0.0' @@ -3944,20 +3937,20 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/router-core@1.158.1': - resolution: {integrity: sha512-8B9X3GzN1JWsqa+OTgg2k+LrayLQYmgtv26b96difyrRS32DaDBvEpU3xXDaLNmi/+zoqG1ffAcDT4D6tyC2hw==} + '@tanstack/router-core@1.158.4': + resolution: {integrity: sha512-KikgYdyrEFqsjjgv9pMhDTMmASMAyFRvUiKFdQPQtXq3aD1qv/zck4CbA4bfzp9N9nYu/qvWwU1mlYU4u5JeXg==} engines: {node: '>=12'} - '@tanstack/router-generator@1.158.1': - resolution: {integrity: sha512-geBpsIxJNvdjw2kt/Ii/j68hIUvfGnra0HKlGrDZw8/Ny4AJ2nnOcszUlZRbuQyxByk05r4lneOShKy5V5MUCQ==} + '@tanstack/router-generator@1.158.4': + resolution: {integrity: sha512-RQmqMTT0oV8dS/3Glcq9SPzDZqOPyKb/LVFUkNoTfMwW88WyGnQcYqZAkmVk/CGBWWDfwObOUZoGq5jTF7bG8w==} engines: {node: '>=12'} - '@tanstack/router-plugin@1.158.1': - resolution: {integrity: sha512-IPCnf1CBc0jnczuy65+3iBaoABv5TKhOJ1YLzwel4kb9D8Abcq0vF8ooR5FiPmaGnree/z3SvjgHe5eQtgcsSQ==} + '@tanstack/router-plugin@1.158.4': + resolution: {integrity: sha512-g2sytAhljw6Jd6Klu37OZ75+o+vhiGdbWtnBy/4rYLC4NN6hSnjgJQRI3+h1CI1KQ4EUgsZYZr/hgE1KHoiWYQ==} engines: {node: '>=12'} peerDependencies: '@rsbuild/core': '>=1.0.2' - '@tanstack/react-router': ^1.158.1 + '@tanstack/react-router': ^1.158.4 vite: '>=5.0.0 || >=6.0.0 || >=7.0.0' vite-plugin-solid: ^2.11.10 webpack: '>=5.92.0' @@ -3973,8 +3966,8 @@ packages: webpack: optional: true - '@tanstack/router-ssr-query-core@1.158.1': - resolution: {integrity: sha512-Paucg5utPgSFS0aJOFKefKPNJaYUneTH4gXI32dB5EiNgjFuXcrXMWT4n0Hm2t0WUgMxn7KIcGgD2cB9wuOGAw==} + '@tanstack/router-ssr-query-core@1.158.4': + resolution: {integrity: sha512-gZRx0pGaRc7NPrwQSAfnn/DVWEsd01cf5TaW5yTyf3R5ZP/I++KNEW3lBXyRo1RyKedPC45R+Id6HpDeEaidyg==} engines: {node: '>=12'} peerDependencies: '@tanstack/query-core': '>=5.90.0' @@ -3984,26 +3977,26 @@ packages: resolution: {integrity: sha512-qZ76eaLKU6Ae9iI/mc5zizBX149DXXZkBVVO3/QRIll79uKLJZHQlMKR++2ba7JsciBWz1pgpIBcCJPE9S0LVg==} engines: {node: '>=12'} - '@tanstack/start-client-core@1.158.3': - resolution: {integrity: sha512-PAZfD0kEi6GHKFPJK2vn5CKTYZ503YVCGgJZ+RCyfH55/6qTzaPrY68Pk5nhUxp7WmjrAJEhGqr1V42hLGM5EA==} + '@tanstack/start-client-core@1.158.4': + resolution: {integrity: sha512-qpUYwJMMCEKgJuMz2CJLt53XrObi1BSjV1gG5SgBWRRVOHL8zky55tu1fEqHEa26jTTA6mUcBnPzYE8vIjRpAw==} engines: {node: '>=22.12.0'} '@tanstack/start-fn-stubs@1.154.7': resolution: {integrity: sha512-D69B78L6pcFN5X5PHaydv7CScQcKLzJeEYqs7jpuyyqGQHSUIZUjS955j+Sir8cHhuDIovCe2LmsYHeZfWf3dQ==} engines: {node: '>=22.12.0'} - '@tanstack/start-plugin-core@1.158.3': - resolution: {integrity: sha512-ZXC++5jXHuApsQpi33eK3WsQSuiCOVFtYE5oAU0fkn2+iV8NpXdqO2RzpQCVo2/Co7LcKulPm4OBdsgYIkuHYQ==} + '@tanstack/start-plugin-core@1.159.0': + resolution: {integrity: sha512-HGcji+Mhste9mDKUlKpRPfoIOaURr7UqQZ3AMb+6zpbXumc+apYW/CvlvWdF/hoZGBSVAniFpwXgV5L5IimnhA==} engines: {node: '>=22.12.0'} peerDependencies: vite: '>=7.0.0' - '@tanstack/start-server-core@1.158.3': - resolution: {integrity: sha512-xPjPALT9M8ZsAcbzlk0Yhj1P48O6q8VUX7S5MJ8psrxh0CIo6nhz2Z5NOSuejjJ6YDSh91KZMNMskHW+MNJpcQ==} + '@tanstack/start-server-core@1.159.0': + resolution: {integrity: sha512-oE9UkWc7uIDvjAOsmzZ65Vz+JLb4S+bhMLGjx84lWY0G+GelJJvdr0rQiUFTWPIsbIxO2pdyIY995H55VUcowg==} engines: {node: '>=22.12.0'} - '@tanstack/start-storage-context@1.158.1': - resolution: {integrity: sha512-0VJt3lUPylglgNmquHs5M4xmfudqEPGWItlvxVEFjrIPoZLHZ098TDSHSycra4RlRbtvlvD6qt0k745ncI0OHw==} + '@tanstack/start-storage-context@1.158.4': + resolution: {integrity: sha512-tz70q/6LTytstBIMRYt5GDRjPJPOHjnPNay85RJdq9ZlQKryeDThnshEttlBTDAxZP7wtwOv00lcAgFLFGP1hA==} engines: {node: '>=22.12.0'} '@tanstack/store@0.8.0': @@ -4013,8 +4006,8 @@ packages: resolution: {integrity: sha512-cHHDnewHozgjpI+MIVp9tcib6lYEQK5MyUr0ChHpHFGBl8Xei55rohFK0I0ve/GKoHeioaK42Smd8OixPp6CTg==} engines: {node: '>=12'} - '@tanstack/zod-adapter@1.158.1': - resolution: {integrity: sha512-aaC28Y7gA1nPJNLklxa6yaCqe230uiQ89OL0fP2QNSQjCxEtqRMlicUWMMPM37wEdm4yZWMFfXuXKGXY1dAoag==} + '@tanstack/zod-adapter@1.158.4': + resolution: {integrity: sha512-tjCwJIbWiAae7JOWwgQ5Exc7YZgWLy3pjnXQ8/ON64p0+tHGDI8usrL5dJMEhRNOt7T3+3ytmRNRaVj8vVDvRw==} engines: {node: '>=12'} peerDependencies: '@tanstack/react-router': '>=1.43.2' @@ -4330,14 +4323,14 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} - ai-sdk-ollama@3.4.0: - resolution: {integrity: sha512-iJQ4XwMOgX+mQO9NpJnj7S3AGiaEjBSO1ahc946mlp57T7t81dr1TtB/hoTZuqpjvV3jZY17Vv3LS4NI5qsmqQ==} + ai-sdk-ollama@3.5.0: + resolution: {integrity: sha512-G7cD0AEz3MJ3kGuhotDnscH2YnlX2gEdIuY7Y3A4fFxhocEsvS6bAiyO2A4EcPVEa5a6iniJaYTlqDKVXPis4Q==} engines: {node: '>=22'} peerDependencies: - ai: ^6.0.70 + ai: ^6.0.77 - ai@6.0.75: - resolution: {integrity: sha512-pT1xtwaE5Un261GO9Mt9DZ2rv26v+gQZNRXo433CNAMo0JDYs2ef+5MvcNkBe1jXCY9XrXxUG/VT6Y6/9S2SWg==} + ai@6.0.77: + resolution: {integrity: sha512-tyyhrRpCRFVlivdNIFLK8cexSBB2jwTqO0z1qJQagk+UxZ+MW8h5V8xsvvb+xdKDY482Y8KAm0mr7TDnPKvvlw==} engines: {node: '>=18'} peerDependencies: zod: ^3.25.76 || ^4.1.8 @@ -4513,8 +4506,8 @@ packages: resolution: {integrity: sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==} engines: {node: '>= 18'} - better-auth@1.5.0-beta.11: - resolution: {integrity: sha512-rf5Rq217KyN+EuagsYHpzrXZodhPLrUJ6ViTUbmY+HZBvfvHtHxREOrl0Sv+wAXL8XND9P1n2eTIcVHDIuBTvw==} + better-auth@1.5.0-beta.13: + resolution: {integrity: sha512-J/48ye6NRNQH5Wj1FXrQdBR6/4l1ohOCVhHIqTB3u48jNCwmXwarhDcctTXM3/FGSffYmY8Hjc+kvbxd5IM5Ug==} peerDependencies: '@lynx-js/react': '*' '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 @@ -4575,8 +4568,8 @@ packages: vue: optional: true - better-call@1.2.0: - resolution: {integrity: sha512-7msprrikJah2Pq+OPJOUkqqlDOpVQysBPfxLfXQZr1XeIPqUD3O5z6qxh0vsAU8m/GDFbJD0n1a4vvTnekM7Kw==} + better-call@1.2.1: + resolution: {integrity: sha512-Ccgd5hj2Fmtu9Vjb9APXNYxutJWPRDhJanWAzFLwSzYAiOvkXN61OmYdvSirfrL2Z7REuK7TRklP8SIbZRlGwA==} peerDependencies: zod: ^4.0.0 peerDependenciesMeta: @@ -5387,6 +5380,9 @@ packages: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} + fast-json-patch@3.1.1: + resolution: {integrity: sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==} + fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -6537,8 +6533,8 @@ packages: node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} - nodemailer@8.0.0: - resolution: {integrity: sha512-xvVJf/f0bzmNpnRIbhCp/IKxaHgJ6QynvUbLXzzMRPG3LDQr5oXkYuw4uDFyFYs8cge8agwwrJAXZsd4hhMquw==} + nodemailer@8.0.1: + resolution: {integrity: sha512-5kcldIXmaEjZcHR6F28IKGSgpmZHaF1IXLWFTG+Xh3S+Cce4MiakLtWY+PlBU69fLbRa8HlaGIrC/QolUpHkhg==} engines: {node: '>=6.0.0'} nopt@9.0.0: @@ -8215,7 +8211,7 @@ snapshots: '@ai-sdk/provider-utils': 4.0.14(zod@4.3.6) zod: 4.3.6 - '@ai-sdk/gateway@3.0.37(zod@4.3.6)': + '@ai-sdk/gateway@3.0.39(zod@4.3.6)': dependencies: '@ai-sdk/provider': 3.0.8 '@ai-sdk/provider-utils': 4.0.14(zod@4.3.6) @@ -8234,13 +8230,6 @@ snapshots: '@ai-sdk/provider-utils': 4.0.14(zod@4.3.6) zod: 4.3.6 - '@ai-sdk/provider-utils@4.0.13(zod@4.3.6)': - dependencies: - '@ai-sdk/provider': 3.0.7 - '@standard-schema/spec': 1.1.0 - eventsource-parser: 3.0.6 - zod: 4.3.6 - '@ai-sdk/provider-utils@4.0.14(zod@4.3.6)': dependencies: '@ai-sdk/provider': 3.0.8 @@ -8248,10 +8237,6 @@ snapshots: eventsource-parser: 3.0.6 zod: 4.3.6 - '@ai-sdk/provider@3.0.7': - dependencies: - json-schema: 0.4.0 - '@ai-sdk/provider@3.0.8': dependencies: json-schema: 0.4.0 @@ -9666,62 +9651,62 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@better-auth/core@1.5.0-beta.11(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0)': + '@better-auth/core@1.5.0-beta.13(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.1(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0)': dependencies: '@better-auth/utils': 0.3.1 '@better-fetch/fetch': 1.1.21 '@standard-schema/spec': 1.1.0 - better-call: 1.2.0(zod@4.3.6) + better-call: 1.2.1(zod@4.3.6) jose: 6.1.3 kysely: 0.28.10 nanostores: 1.1.0 zod: 4.3.6 - '@better-auth/drizzle-adapter@1.5.0-beta.11(@better-auth/core@1.5.0-beta.11(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.1)(drizzle-orm@1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)))': + '@better-auth/drizzle-adapter@1.5.0-beta.13(@better-auth/core@1.5.0-beta.13(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.1(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.1)(drizzle-orm@1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)))': dependencies: - '@better-auth/core': 1.5.0-beta.11(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) + '@better-auth/core': 1.5.0-beta.13(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.1(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) '@better-auth/utils': 0.3.1 drizzle-orm: 1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)) - '@better-auth/kysely-adapter@1.5.0-beta.11(@better-auth/core@1.5.0-beta.11(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.1)(kysely@0.28.10)': + '@better-auth/kysely-adapter@1.5.0-beta.13(@better-auth/core@1.5.0-beta.13(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.1(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.1)(kysely@0.28.10)': dependencies: - '@better-auth/core': 1.5.0-beta.11(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) + '@better-auth/core': 1.5.0-beta.13(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.1(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) '@better-auth/utils': 0.3.1 kysely: 0.28.10 - '@better-auth/memory-adapter@1.5.0-beta.11(@better-auth/core@1.5.0-beta.11(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.1)': + '@better-auth/memory-adapter@1.5.0-beta.13(@better-auth/core@1.5.0-beta.13(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.1(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.1)': dependencies: - '@better-auth/core': 1.5.0-beta.11(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) + '@better-auth/core': 1.5.0-beta.13(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.1(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) '@better-auth/utils': 0.3.1 - '@better-auth/mongo-adapter@1.5.0-beta.11(@better-auth/core@1.5.0-beta.11(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.1)(mongodb@7.0.0(socks@2.8.7))': + '@better-auth/mongo-adapter@1.5.0-beta.13(@better-auth/core@1.5.0-beta.13(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.1(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.1)(mongodb@7.0.0(socks@2.8.7))': dependencies: - '@better-auth/core': 1.5.0-beta.11(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) + '@better-auth/core': 1.5.0-beta.13(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.1(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) '@better-auth/utils': 0.3.1 mongodb: 7.0.0(socks@2.8.7) - '@better-auth/passkey@1.5.0-beta.11(38c18a8d8b45924e68e857ec7a702968)': + '@better-auth/passkey@1.5.0-beta.13(2258e4f412fd167666cad25c7546abd6)': dependencies: - '@better-auth/core': 1.5.0-beta.11(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) + '@better-auth/core': 1.5.0-beta.13(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.1(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) '@better-auth/utils': 0.3.1 '@better-fetch/fetch': 1.1.21 '@simplewebauthn/browser': 13.2.2 '@simplewebauthn/server': 13.2.2 - better-auth: 1.5.0-beta.11(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@tanstack/react-start@1.158.3(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rolldown-vite@7.3.1(@types/node@25.2.1)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)))(drizzle-kit@1.0.0-beta.9-e89174b)(drizzle-orm@1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)))(mongodb@7.0.0(socks@2.8.7))(mysql2@3.15.3)(pg@8.18.0)(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - better-call: 1.2.0(zod@4.3.6) + better-auth: 1.5.0-beta.13(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@tanstack/react-start@1.159.0(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rolldown-vite@7.3.1(@types/node@25.2.1)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)))(drizzle-kit@1.0.0-beta.9-e89174b)(drizzle-orm@1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)))(mongodb@7.0.0(socks@2.8.7))(mysql2@3.15.3)(pg@8.18.0)(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + better-call: 1.2.1(zod@4.3.6) nanostores: 1.1.0 zod: 4.3.6 - '@better-auth/prisma-adapter@1.5.0-beta.11(@better-auth/core@1.5.0-beta.11(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.1)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))': + '@better-auth/prisma-adapter@1.5.0-beta.13(@better-auth/core@1.5.0-beta.13(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.1(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.1)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))': dependencies: - '@better-auth/core': 1.5.0-beta.11(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) + '@better-auth/core': 1.5.0-beta.13(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.1(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) '@better-auth/utils': 0.3.1 '@prisma/client': 7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3) prisma: 7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) - '@better-auth/telemetry@1.5.0-beta.11(@better-auth/core@1.5.0-beta.11(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))': + '@better-auth/telemetry@1.5.0-beta.13(@better-auth/core@1.5.0-beta.13(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.1(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))': dependencies: - '@better-auth/core': 1.5.0-beta.11(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) + '@better-auth/core': 1.5.0-beta.13(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.1(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) '@better-auth/utils': 0.3.1 '@better-fetch/fetch': 1.1.21 @@ -12314,59 +12299,59 @@ snapshots: '@tanstack/query-core': 5.90.20 react: 19.2.4 - '@tanstack/react-router-ssr-query@1.158.1(@tanstack/query-core@5.90.20)(@tanstack/react-query@5.90.20(react@19.2.4))(@tanstack/react-router@1.158.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.158.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@tanstack/react-router-ssr-query@1.158.4(@tanstack/query-core@5.90.20)(@tanstack/react-query@5.90.20(react@19.2.4))(@tanstack/react-router@1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.158.4)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@tanstack/query-core': 5.90.20 '@tanstack/react-query': 5.90.20(react@19.2.4) - '@tanstack/react-router': 1.158.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@tanstack/router-ssr-query-core': 1.158.1(@tanstack/query-core@5.90.20)(@tanstack/router-core@1.158.1) + '@tanstack/react-router': 1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tanstack/router-ssr-query-core': 1.158.4(@tanstack/query-core@5.90.20)(@tanstack/router-core@1.158.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) transitivePeerDependencies: - '@tanstack/router-core' - '@tanstack/react-router@1.158.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@tanstack/react-router@1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@tanstack/history': 1.154.14 '@tanstack/react-store': 0.8.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@tanstack/router-core': 1.158.1 + '@tanstack/router-core': 1.158.4 isbot: 5.1.33 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - '@tanstack/react-start-client@1.158.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@tanstack/react-start-client@1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@tanstack/react-router': 1.158.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@tanstack/router-core': 1.158.1 - '@tanstack/start-client-core': 1.158.3 + '@tanstack/react-router': 1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tanstack/router-core': 1.158.4 + '@tanstack/start-client-core': 1.158.4 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - '@tanstack/react-start-server@1.158.3(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@tanstack/react-start-server@1.159.0(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@tanstack/history': 1.154.14 - '@tanstack/react-router': 1.158.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@tanstack/router-core': 1.158.1 - '@tanstack/start-client-core': 1.158.3 - '@tanstack/start-server-core': 1.158.3(crossws@0.4.3(srvx@0.10.1)) + '@tanstack/react-router': 1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tanstack/router-core': 1.158.4 + '@tanstack/start-client-core': 1.158.4 + '@tanstack/start-server-core': 1.159.0(crossws@0.4.3(srvx@0.10.1)) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) transitivePeerDependencies: - crossws - '@tanstack/react-start@1.158.3(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rolldown-vite@7.3.1(@types/node@25.2.1)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0))': + '@tanstack/react-start@1.159.0(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rolldown-vite@7.3.1(@types/node@25.2.1)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0))': dependencies: - '@tanstack/react-router': 1.158.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@tanstack/react-start-client': 1.158.3(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@tanstack/react-start-server': 1.158.3(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tanstack/react-router': 1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tanstack/react-start-client': 1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tanstack/react-start-server': 1.159.0(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/router-utils': 1.158.0 - '@tanstack/start-client-core': 1.158.3 - '@tanstack/start-plugin-core': 1.158.3(@tanstack/react-router@1.158.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(crossws@0.4.3(srvx@0.10.1))(rolldown-vite@7.3.1(@types/node@25.2.1)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)) - '@tanstack/start-server-core': 1.158.3(crossws@0.4.3(srvx@0.10.1)) + '@tanstack/start-client-core': 1.158.4 + '@tanstack/start-plugin-core': 1.159.0(@tanstack/react-router@1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(crossws@0.4.3(srvx@0.10.1))(rolldown-vite@7.3.1(@types/node@25.2.1)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)) + '@tanstack/start-server-core': 1.159.0(crossws@0.4.3(srvx@0.10.1)) pathe: 2.0.3 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) @@ -12385,7 +12370,7 @@ snapshots: react-dom: 19.2.4(react@19.2.4) use-sync-external-store: 1.6.0(react@19.2.4) - '@tanstack/router-core@1.158.1': + '@tanstack/router-core@1.158.4': dependencies: '@tanstack/history': 1.154.14 '@tanstack/store': 0.8.0 @@ -12395,9 +12380,9 @@ snapshots: tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - '@tanstack/router-generator@1.158.1': + '@tanstack/router-generator@1.158.4': dependencies: - '@tanstack/router-core': 1.158.1 + '@tanstack/router-core': 1.158.4 '@tanstack/router-utils': 1.158.0 '@tanstack/virtual-file-routes': 1.154.7 prettier: 3.8.0 @@ -12408,7 +12393,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.158.1(@tanstack/react-router@1.158.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(rolldown-vite@7.3.1(@types/node@25.2.1)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0))': + '@tanstack/router-plugin@1.158.4(@tanstack/react-router@1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(rolldown-vite@7.3.1(@types/node@25.2.1)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) @@ -12416,23 +12401,23 @@ snapshots: '@babel/template': 7.28.6 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 - '@tanstack/router-core': 1.158.1 - '@tanstack/router-generator': 1.158.1 + '@tanstack/router-core': 1.158.4 + '@tanstack/router-generator': 1.158.4 '@tanstack/router-utils': 1.158.0 '@tanstack/virtual-file-routes': 1.154.7 chokidar: 3.6.0 unplugin: 2.3.11 zod: 3.25.76 optionalDependencies: - '@tanstack/react-router': 1.158.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tanstack/react-router': 1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) vite: rolldown-vite@7.3.1(@types/node@25.2.1)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0) transitivePeerDependencies: - supports-color - '@tanstack/router-ssr-query-core@1.158.1(@tanstack/query-core@5.90.20)(@tanstack/router-core@1.158.1)': + '@tanstack/router-ssr-query-core@1.158.4(@tanstack/query-core@5.90.20)(@tanstack/router-core@1.158.4)': dependencies: '@tanstack/query-core': 5.90.20 - '@tanstack/router-core': 1.158.1 + '@tanstack/router-core': 1.158.4 '@tanstack/router-utils@1.158.0': dependencies: @@ -12448,29 +12433,29 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/start-client-core@1.158.3': + '@tanstack/start-client-core@1.158.4': dependencies: - '@tanstack/router-core': 1.158.1 + '@tanstack/router-core': 1.158.4 '@tanstack/start-fn-stubs': 1.154.7 - '@tanstack/start-storage-context': 1.158.1 + '@tanstack/start-storage-context': 1.158.4 seroval: 1.4.2 tiny-invariant: 1.3.3 tiny-warning: 1.0.3 '@tanstack/start-fn-stubs@1.154.7': {} - '@tanstack/start-plugin-core@1.158.3(@tanstack/react-router@1.158.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(crossws@0.4.3(srvx@0.10.1))(rolldown-vite@7.3.1(@types/node@25.2.1)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0))': + '@tanstack/start-plugin-core@1.159.0(@tanstack/react-router@1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(crossws@0.4.3(srvx@0.10.1))(rolldown-vite@7.3.1(@types/node@25.2.1)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0))': dependencies: '@babel/code-frame': 7.27.1 '@babel/core': 7.29.0 '@babel/types': 7.29.0 '@rolldown/pluginutils': 1.0.0-beta.40 - '@tanstack/router-core': 1.158.1 - '@tanstack/router-generator': 1.158.1 - '@tanstack/router-plugin': 1.158.1(@tanstack/react-router@1.158.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(rolldown-vite@7.3.1(@types/node@25.2.1)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)) + '@tanstack/router-core': 1.158.4 + '@tanstack/router-generator': 1.158.4 + '@tanstack/router-plugin': 1.158.4(@tanstack/react-router@1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(rolldown-vite@7.3.1(@types/node@25.2.1)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)) '@tanstack/router-utils': 1.158.0 - '@tanstack/start-client-core': 1.158.3 - '@tanstack/start-server-core': 1.158.3(crossws@0.4.3(srvx@0.10.1)) + '@tanstack/start-client-core': 1.158.4 + '@tanstack/start-server-core': 1.159.0(crossws@0.4.3(srvx@0.10.1)) cheerio: 1.1.2 exsolve: 1.0.8 pathe: 2.0.3 @@ -12489,29 +12474,29 @@ snapshots: - vite-plugin-solid - webpack - '@tanstack/start-server-core@1.158.3(crossws@0.4.3(srvx@0.10.1))': + '@tanstack/start-server-core@1.159.0(crossws@0.4.3(srvx@0.10.1))': dependencies: '@tanstack/history': 1.154.14 - '@tanstack/router-core': 1.158.1 - '@tanstack/start-client-core': 1.158.3 - '@tanstack/start-storage-context': 1.158.1 + '@tanstack/router-core': 1.158.4 + '@tanstack/start-client-core': 1.158.4 + '@tanstack/start-storage-context': 1.158.4 h3-v2: h3@2.0.1-rc.11(crossws@0.4.3(srvx@0.10.1)) seroval: 1.4.2 tiny-invariant: 1.3.3 transitivePeerDependencies: - crossws - '@tanstack/start-storage-context@1.158.1': + '@tanstack/start-storage-context@1.158.4': dependencies: - '@tanstack/router-core': 1.158.1 + '@tanstack/router-core': 1.158.4 '@tanstack/store@0.8.0': {} '@tanstack/virtual-file-routes@1.154.7': {} - '@tanstack/zod-adapter@1.158.1(@tanstack/react-router@1.158.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(zod@4.3.6)': + '@tanstack/zod-adapter@1.158.4(@tanstack/react-router@1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(zod@4.3.6)': dependencies: - '@tanstack/react-router': 1.158.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tanstack/react-router': 1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4) zod: 4.3.6 '@tediousjs/connection-string@0.5.0': {} @@ -12869,18 +12854,18 @@ snapshots: agent-base@7.1.4: {} - ai-sdk-ollama@3.4.0(ai@6.0.75(zod@4.3.6))(zod@4.3.6): + ai-sdk-ollama@3.5.0(ai@6.0.77(zod@4.3.6))(zod@4.3.6): dependencies: - '@ai-sdk/provider': 3.0.7 - '@ai-sdk/provider-utils': 4.0.13(zod@4.3.6) - ai: 6.0.75(zod@4.3.6) + '@ai-sdk/provider': 3.0.8 + '@ai-sdk/provider-utils': 4.0.14(zod@4.3.6) + ai: 6.0.77(zod@4.3.6) ollama: 0.6.3 transitivePeerDependencies: - zod - ai@6.0.75(zod@4.3.6): + ai@6.0.77(zod@4.3.6): dependencies: - '@ai-sdk/gateway': 3.0.37(zod@4.3.6) + '@ai-sdk/gateway': 3.0.39(zod@4.3.6) '@ai-sdk/provider': 3.0.8 '@ai-sdk/provider-utils': 4.0.14(zod@4.3.6) '@opentelemetry/api': 1.9.0 @@ -13054,20 +13039,20 @@ snapshots: node-addon-api: 8.5.0 node-gyp-build: 4.8.4 - better-auth@1.5.0-beta.11(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@tanstack/react-start@1.158.3(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rolldown-vite@7.3.1(@types/node@25.2.1)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)))(drizzle-kit@1.0.0-beta.9-e89174b)(drizzle-orm@1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)))(mongodb@7.0.0(socks@2.8.7))(mysql2@3.15.3)(pg@8.18.0)(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + better-auth@1.5.0-beta.13(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@tanstack/react-start@1.159.0(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rolldown-vite@7.3.1(@types/node@25.2.1)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)))(drizzle-kit@1.0.0-beta.9-e89174b)(drizzle-orm@1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)))(mongodb@7.0.0(socks@2.8.7))(mysql2@3.15.3)(pg@8.18.0)(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: - '@better-auth/core': 1.5.0-beta.11(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) - '@better-auth/drizzle-adapter': 1.5.0-beta.11(@better-auth/core@1.5.0-beta.11(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.1)(drizzle-orm@1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))) - '@better-auth/kysely-adapter': 1.5.0-beta.11(@better-auth/core@1.5.0-beta.11(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.1)(kysely@0.28.10) - '@better-auth/memory-adapter': 1.5.0-beta.11(@better-auth/core@1.5.0-beta.11(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.1) - '@better-auth/mongo-adapter': 1.5.0-beta.11(@better-auth/core@1.5.0-beta.11(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.1)(mongodb@7.0.0(socks@2.8.7)) - '@better-auth/prisma-adapter': 1.5.0-beta.11(@better-auth/core@1.5.0-beta.11(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.1)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)) - '@better-auth/telemetry': 1.5.0-beta.11(@better-auth/core@1.5.0-beta.11(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.0(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0)) + '@better-auth/core': 1.5.0-beta.13(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.1(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0) + '@better-auth/drizzle-adapter': 1.5.0-beta.13(@better-auth/core@1.5.0-beta.13(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.1(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.1)(drizzle-orm@1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))) + '@better-auth/kysely-adapter': 1.5.0-beta.13(@better-auth/core@1.5.0-beta.13(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.1(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.1)(kysely@0.28.10) + '@better-auth/memory-adapter': 1.5.0-beta.13(@better-auth/core@1.5.0-beta.13(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.1(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.1) + '@better-auth/mongo-adapter': 1.5.0-beta.13(@better-auth/core@1.5.0-beta.13(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.1(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.1)(mongodb@7.0.0(socks@2.8.7)) + '@better-auth/prisma-adapter': 1.5.0-beta.13(@better-auth/core@1.5.0-beta.13(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.1(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0))(@better-auth/utils@0.3.1)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)) + '@better-auth/telemetry': 1.5.0-beta.13(@better-auth/core@1.5.0-beta.13(@better-auth/utils@0.3.1)(@better-fetch/fetch@1.1.21)(better-call@1.2.1(zod@4.3.6))(jose@6.1.3)(kysely@0.28.10)(nanostores@1.1.0)) '@better-auth/utils': 0.3.1 '@better-fetch/fetch': 1.1.21 '@noble/ciphers': 2.1.1 '@noble/hashes': 2.0.1 - better-call: 1.2.0(zod@4.3.6) + better-call: 1.2.1(zod@4.3.6) defu: 6.1.4 jose: 6.1.3 kysely: 0.28.10 @@ -13075,7 +13060,7 @@ snapshots: zod: 4.3.6 optionalDependencies: '@prisma/client': 7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3) - '@tanstack/react-start': 1.158.3(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rolldown-vite@7.3.1(@types/node@25.2.1)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)) + '@tanstack/react-start': 1.159.0(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rolldown-vite@7.3.1(@types/node@25.2.1)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)) drizzle-kit: 1.0.0-beta.9-e89174b drizzle-orm: 1.0.0-beta.9-e89174b(@electric-sql/pglite@0.3.2)(@opentelemetry/api@1.9.0)(@prisma/client@7.2.0(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3))(typescript@5.9.3))(@types/mssql@9.1.9)(@types/pg@8.16.0)(mssql@11.0.1)(mysql2@3.15.3)(pg@8.18.0)(postgres@3.4.7)(prisma@7.2.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)) mongodb: 7.0.0(socks@2.8.7) @@ -13085,7 +13070,7 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - better-call@1.2.0(zod@4.3.6): + better-call@1.2.1(zod@4.3.6): dependencies: '@better-auth/utils': 0.3.1 '@better-fetch/fetch': 1.1.21 @@ -13943,6 +13928,8 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 + fast-json-patch@3.1.1: {} + fast-json-stable-stringify@2.1.0: {} fast-uri@3.1.0: {} @@ -15006,7 +14993,7 @@ snapshots: node-releases@2.0.27: {} - nodemailer@8.0.0: {} + nodemailer@8.0.1: {} nopt@9.0.0: dependencies: diff --git a/src/integrations/orpc/dto/resume.ts b/src/integrations/orpc/dto/resume.ts index b56260afd..32ea4b55d 100644 --- a/src/integrations/orpc/dto/resume.ts +++ b/src/integrations/orpc/dto/resume.ts @@ -77,6 +77,26 @@ export const resumeDto = { output: z.void(), }, + patch: { + input: z.object({ + id: z.string().describe("The ID of the resume to patch."), + operations: z + .array( + z.object({ + op: z.enum(["add", "remove", "replace", "move", "copy", "test"]), + path: z.string(), + value: z.any().optional(), + from: z.string().optional(), + }), + ) + .min(1) + .describe("An array of JSON Patch (RFC 6902) operations to apply to the resume data."), + }), + output: resumeSchema + .omit({ password: true, userId: true, createdAt: true, updatedAt: true }) + .extend({ hasPassword: z.boolean() }), + }, + duplicate: { input: resumeSchema.pick({ id: true, name: true, slug: true, tags: true }), output: z.string().describe("The ID of the duplicated resume."), diff --git a/src/integrations/orpc/router/resume.ts b/src/integrations/orpc/router/resume.ts index 55c3f9d96..1ae22c41e 100644 --- a/src/integrations/orpc/router/resume.ts +++ b/src/integrations/orpc/router/resume.ts @@ -1,3 +1,4 @@ +import type { Operation } from "fast-json-patch"; import z from "zod"; import { sampleResumeData } from "@/schema/resume/sample"; import { generateRandomName, slugify } from "@/utils/string"; @@ -193,6 +194,31 @@ export const resumeRouter = { }); }), + patch: protectedProcedure + .route({ + method: "PATCH", + path: "/resume/{id}", + tags: ["Resume"], + summary: "Patch a resume", + description: + "Apply JSON Patch (RFC 6902) operations to partially update a resume's data. This allows you to make small, targeted changes without sending the entire resume object.", + }) + .input(resumeDto.patch.input) + .output(resumeDto.patch.output) + .errors({ + INVALID_PATCH_OPERATIONS: { + message: "The patch operations are invalid or produced an invalid resume.", + status: 400, + }, + }) + .handler(async ({ context, input }) => { + return await resumeService.patch({ + id: input.id, + userId: context.user.id, + operations: input.operations as Operation[], + }); + }), + setLocked: protectedProcedure .route({ method: "POST", diff --git a/src/integrations/orpc/services/resume.ts b/src/integrations/orpc/services/resume.ts index 47ba86c39..0c75cd08a 100644 --- a/src/integrations/orpc/services/resume.ts +++ b/src/integrations/orpc/services/resume.ts @@ -1,6 +1,7 @@ import { ORPCError } from "@orpc/client"; import { and, arrayContains, asc, desc, eq, sql } from "drizzle-orm"; import { get } from "es-toolkit/compat"; +import type { Operation } from "fast-json-patch"; import { match } from "ts-pattern"; import { schema } from "@/integrations/drizzle"; import { db } from "@/integrations/drizzle/client"; @@ -9,6 +10,7 @@ import { defaultResumeData } from "@/schema/resume/data"; import { env } from "@/utils/env"; import type { Locale } from "@/utils/locale"; import { hashPassword } from "@/utils/password"; +import { applyResumePatches, ResumePatchError } from "@/utils/resume/patch"; import { generateId } from "@/utils/string"; import { hasResumeAccess } from "../helpers/resume-access"; import { getStorageService } from "./storage"; @@ -317,6 +319,54 @@ export const resumeService = { } }, + patch: async (input: { id: string; userId: string; operations: Operation[] }) => { + const [existing] = await db + .select({ data: schema.resume.data, isLocked: schema.resume.isLocked }) + .from(schema.resume) + .where(and(eq(schema.resume.id, input.id), eq(schema.resume.userId, input.userId))); + + if (!existing) throw new ORPCError("NOT_FOUND"); + if (existing.isLocked) throw new ORPCError("RESUME_LOCKED"); + + let patchedData: ResumeData; + + try { + patchedData = applyResumePatches(existing.data, input.operations); + } catch (error) { + if (error instanceof ResumePatchError) { + throw new ORPCError("INVALID_PATCH_OPERATIONS", { + status: 400, + message: error.message, + data: { code: error.code, index: error.index, operation: error.operation }, + }); + } + + throw new ORPCError("INVALID_PATCH_OPERATIONS", { + status: 400, + message: error instanceof Error ? error.message : "Failed to apply patch operations", + }); + } + + const [resume] = await db + .update(schema.resume) + .set({ data: patchedData }) + .where( + and(eq(schema.resume.id, input.id), eq(schema.resume.isLocked, false), eq(schema.resume.userId, input.userId)), + ) + .returning({ + id: schema.resume.id, + name: schema.resume.name, + slug: schema.resume.slug, + tags: schema.resume.tags, + data: schema.resume.data, + isPublic: schema.resume.isPublic, + isLocked: schema.resume.isLocked, + hasPassword: sql`${schema.resume.password} IS NOT NULL`, + }); + + return resume; + }, + setLocked: async (input: { id: string; userId: string; isLocked: boolean }) => { await db .update(schema.resume) diff --git a/src/utils/resume/patch.ts b/src/utils/resume/patch.ts new file mode 100644 index 000000000..ffcc5d299 --- /dev/null +++ b/src/utils/resume/patch.ts @@ -0,0 +1,109 @@ +import type { JsonPatchError, Operation } from "fast-json-patch"; +import jsonpatch from "fast-json-patch"; +import { type ResumeData, resumeDataSchema } from "@/schema/resume/data"; + +export type { Operation }; + +/** + * A structured error thrown when a JSON Patch operation fails. + * Contains only the relevant details -- never the full document tree. + */ +export class ResumePatchError extends Error { + /** The error code from `fast-json-patch`, e.g. `TEST_OPERATION_FAILED`. */ + code: string; + /** The zero-based index of the failing operation in the operations array. */ + index: number; + /** The operation object that caused the failure. */ + operation: Operation; + + constructor(code: string, message: string, index: number, operation: Operation) { + super(message); + this.name = "ResumePatchError"; + this.code = code; + this.index = index; + this.operation = operation; + } +} + +/** + * Human-readable messages for each `fast-json-patch` error code. + * These are returned to the API consumer instead of the raw library output. + */ +const patchErrorMessages: Record = { + SEQUENCE_NOT_AN_ARRAY: "Patch sequence must be an array.", + OPERATION_NOT_AN_OBJECT: "Operation is not an object.", + OPERATION_OP_INVALID: "Operation `op` property is not one of the operations defined in RFC 6902.", + OPERATION_PATH_INVALID: "Operation `path` property is not a valid JSON Pointer string.", + OPERATION_FROM_REQUIRED: "Operation `from` property is required for `move` and `copy` operations.", + OPERATION_VALUE_REQUIRED: "Operation `value` property is required for `add`, `replace`, and `test` operations.", + OPERATION_VALUE_CANNOT_CONTAIN_UNDEFINED: + "Operation `value` contains an `undefined` value, which is not valid in JSON.", + OPERATION_PATH_CANNOT_ADD: "Cannot perform an `add` operation at the desired path.", + OPERATION_PATH_UNRESOLVABLE: "Cannot perform the operation at a path that does not exist.", + OPERATION_FROM_UNRESOLVABLE: "Cannot perform the operation from a path that does not exist.", + OPERATION_PATH_ILLEGAL_ARRAY_INDEX: "Array index in path must be an unsigned base-10 integer.", + OPERATION_VALUE_OUT_OF_BOUNDS: "The specified array index is greater than the number of elements in the array.", + TEST_OPERATION_FAILED: "Test operation failed -- the value at the given path did not match the expected value.", +}; + +/** + * Checks whether an error is a `JsonPatchError` from `fast-json-patch`. + * The library doesn't export the class directly, so we duck-type it. + */ +function isJsonPatchError(error: unknown): error is JsonPatchError { + return error instanceof Error && "index" in error && "operation" in error; +} + +/** + * Converts a `JsonPatchError` into a clean `ResumePatchError` that omits the document tree. + */ +function toResumePatchError(error: JsonPatchError): ResumePatchError { + const code = error.name; + const message = patchErrorMessages[code] ?? error.message; + const index = error.index ?? 0; + const operation = error.operation as Operation; + + return new ResumePatchError(code, message, index, operation); +} + +/** + * Applies an array of JSON Patch (RFC 6902) operations to a `ResumeData` object. + * + * This function validates the operations before applying them, then validates the + * resulting document against the `resumeDataSchema` to ensure the patched data is + * still a valid resume. + * + * The original `data` object is not mutated; a deep clone is created internally. + * + * @see https://docs.rxresu.me/guides/using-the-patch-api — for usage examples and API details. + * @see https://datatracker.ietf.org/doc/html/rfc6902 — JSON Patch specification. + * + * @param data - The current resume data to patch. + * @param operations - An array of JSON Patch operations to apply. + * @returns The patched and validated `ResumeData` object. + * @throws {ResumePatchError} If the operations are structurally invalid or target non-existent paths. + * @throws {ResumePatchError} If a `test` operation does not match. + * @throws {Error} If the patched document does not conform to the `ResumeData` schema. + */ +export function applyResumePatches(data: ResumeData, operations: Operation[]): ResumeData { + // Validate operations structurally before applying. + const validationError = jsonpatch.validate(operations, data); + if (validationError) throw toResumePatchError(validationError); + + // Apply operations. applyPatch throws on `test` failures. + let patched: ResumeData; + + try { + const result = jsonpatch.applyPatch(data, operations, false, false); + patched = result.newDocument; + } catch (error: unknown) { + if (isJsonPatchError(error)) throw toResumePatchError(error); + throw error; + } + + // Validate the result still conforms to ResumeData. + const parsed = resumeDataSchema.safeParse(patched); + if (!parsed.success) throw new Error(`Patch produced invalid resume data: ${parsed.error.message}`); + + return parsed.data; +}