feat: Step 6 - Write-through cache API

Implement the core write-through cache pattern for node creation.
This is the architectural foundation of the application.

Changes:
- Add @google/generative-ai dependency for embeddings
- Create lib/db.ts: SurrealDB connection helper with JWT auth
- Create lib/ai.ts: AI embedding generation using text-embedding-004
- Create app/api/nodes/route.ts: POST endpoint implementing write-through cache

Write-through cache flow:
1. Authenticate user via SurrealDB JWT
2. Publish node to ATproto PDS (source of truth)
3. Generate 768-dimensional embedding via Google AI
4. Cache node + embedding + links in SurrealDB

Updated schema to use 768-dimensional embeddings (text-embedding-004)
instead of 1536 dimensions.

Security:
- Row-level permissions enforced via SurrealDB JWT
- All secrets server-side only
- ATproto OAuth tokens from secure cookies

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-09 00:12:46 +00:00
parent 414bf7d0db
commit e43d6493d2
8 changed files with 278 additions and 3 deletions

50
pnpm-lock.yaml generated
View File

@@ -20,9 +20,15 @@ importers:
'@deepgram/sdk':
specifier: latest
version: 4.11.2(bufferutil@4.0.9)(utf-8-validate@6.0.5)
'@google/generative-ai':
specifier: ^0.24.1
version: 0.24.1
'@mantine/core':
specifier: latest
version: 8.3.6(@mantine/hooks@8.3.6(react@19.2.0))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@mantine/form':
specifier: latest
version: 8.3.6(react@19.2.0)
'@mantine/hooks':
specifier: latest
version: 8.3.6(react@19.2.0)
@@ -41,6 +47,9 @@ importers:
next:
specifier: latest
version: 16.0.1(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
openid-client:
specifier: latest
version: 6.8.1
react:
specifier: latest
version: 19.2.0
@@ -507,6 +516,10 @@ packages:
'@floating-ui/utils@0.2.10':
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
'@google/generative-ai@0.24.1':
resolution: {integrity: sha512-MqO+MLfM6kjxcKoy0p1wRzG3b4ZZXtPI+z2IE26UogS2Cm/XHO+7gGRBh6gcJsOiIVoH93UwKvW4HdgiOZCy9Q==}
engines: {node: '>=18.0.0'}
'@humanfs/core@0.19.1':
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
engines: {node: '>=18.18.0'}
@@ -800,6 +813,11 @@ packages:
react: ^18.x || ^19.x
react-dom: ^18.x || ^19.x
'@mantine/form@8.3.6':
resolution: {integrity: sha512-hIu0KdP1e1Vu7KUQ+cIDpor9UE9vO7iXR3dOMu6GPF3MlHFbwnCjakW9nxSCjP1PRTMwA3m43s4GIt22XfK9tg==}
peerDependencies:
react: ^18.x || ^19.x
'@mantine/hooks@8.3.6':
resolution: {integrity: sha512-liHfaWXHAkLjJy+Bkr29UsCwAoDQ/a64WrM67lksx8F0qqyjR5RQH8zVlhuOjdpQnwtlUkE/YiTvbJiPcoI0bw==}
peerDependencies:
@@ -2153,6 +2171,9 @@ packages:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true
jose@6.1.0:
resolution: {integrity: sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==}
joycon@3.1.1:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
engines: {node: '>=10'}
@@ -2207,6 +2228,10 @@ packages:
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
klona@2.0.6:
resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==}
engines: {node: '>= 8'}
language-subtag-registry@0.3.23:
resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==}
@@ -2406,6 +2431,9 @@ packages:
nth-check@2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
oauth4webapi@3.8.2:
resolution: {integrity: sha512-FzZZ+bht5X0FKe7Mwz3DAVAmlH1BV5blSak/lHMBKz0/EBMhX6B10GlQYI51+oRp8ObJaX0g6pXrAxZh5s8rjw==}
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
@@ -2453,6 +2481,9 @@ packages:
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
engines: {node: '>=18'}
openid-client@6.8.1:
resolution: {integrity: sha512-VoYT6enBo6Vj2j3Q5Ec0AezS+9YGzQo1f5Xc42lreMGlfP4ljiXPKVDvCADh+XHCV/bqPu/wWSiCVXbJKvrODw==}
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
@@ -3692,6 +3723,8 @@ snapshots:
'@floating-ui/utils@0.2.10': {}
'@google/generative-ai@0.24.1': {}
'@humanfs/core@0.19.1': {}
'@humanfs/node@0.16.7':
@@ -3923,6 +3956,12 @@ snapshots:
transitivePeerDependencies:
- '@types/react'
'@mantine/form@8.3.6(react@19.2.0)':
dependencies:
fast-deep-equal: 3.1.3
klona: 2.0.6
react: 19.2.0
'@mantine/hooks@8.3.6(react@19.2.0)':
dependencies:
react: 19.2.0
@@ -5446,6 +5485,8 @@ snapshots:
jiti@2.6.1: {}
jose@6.1.0: {}
joycon@3.1.1: {}
js-tokens@4.0.0: {}
@@ -5505,6 +5546,8 @@ snapshots:
dependencies:
json-buffer: 3.0.1
klona@2.0.6: {}
language-subtag-registry@0.3.23: {}
language-tags@1.0.9:
@@ -5717,6 +5760,8 @@ snapshots:
dependencies:
boolbase: 1.0.0
oauth4webapi@3.8.2: {}
object-assign@4.1.1: {}
object-hash@3.0.0: {}
@@ -5771,6 +5816,11 @@ snapshots:
dependencies:
mimic-function: 5.0.1
openid-client@6.8.1:
dependencies:
jose: 6.1.0
oauth4webapi: 3.8.2
optionator@0.9.4:
dependencies:
deep-is: 0.1.4