diff --git a/.gitignore b/.gitignore index 818622d..5ac2f3c 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,6 @@ Thumbs.db vite.config.js.timestamp-* vite.config.ts.timestamp-* -references \ No newline at end of file +references + +sveltekit-cloudflare-workers \ No newline at end of file diff --git a/lex.config.js b/lex.config.js new file mode 100644 index 0000000..ed6769e --- /dev/null +++ b/lex.config.js @@ -0,0 +1,22 @@ +import { defineLexiconConfig } from "@atcute/lex-cli"; + +export default defineLexiconConfig({ + files: ["lexicons/**/*.json", "lexicons-pulled/**/*.json", "lexicons-generated/**/*.json"], + outdir: "src/lexicon-types/", + imports: ["@atcute/atproto"], + pull: { + outdir: "lexicons-pulled/", + sources: [ + { + type: "atproto", + mode: "nsids", + nsids: [ + "app.blento.card", + "app.blento.page", + "app.bsky.actor.profile", + "site.standard.publication" + ], + }, + ], + }, +}); diff --git a/lexicons-generated/app/blento/card/getRecord.json b/lexicons-generated/app/blento/card/getRecord.json new file mode 100644 index 0000000..513d584 --- /dev/null +++ b/lexicons-generated/app/blento/card/getRecord.json @@ -0,0 +1,106 @@ +{ + "lexicon": 1, + "id": "app.blento.card.getRecord", + "defs": { + "main": { + "type": "query", + "description": "Get a single app.blento.card record by AT URI", + "parameters": { + "type": "params", + "required": [ + "uri" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri", + "description": "AT URI of the record" + }, + "profiles": { + "type": "boolean", + "description": "Include profile + identity info keyed by DID" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "uri", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "cid": { + "type": "string" + }, + "record": { + "type": "ref", + "ref": "app.blento.card#main" + }, + "time_us": { + "type": "integer" + }, + "profiles": { + "type": "array", + "items": { + "type": "ref", + "ref": "#profileEntry" + } + } + } + } + } + }, + "profileEntry": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "cid": { + "type": "string" + }, + "record": { + "type": "unknown" + } + } + } + } +} diff --git a/lexicons-generated/app/blento/card/listRecords.json b/lexicons-generated/app/blento/card/listRecords.json new file mode 100644 index 0000000..415ce01 --- /dev/null +++ b/lexicons-generated/app/blento/card/listRecords.json @@ -0,0 +1,249 @@ +{ + "lexicon": 1, + "id": "app.blento.card.listRecords", + "defs": { + "main": { + "type": "query", + "description": "Query app.blento.card records with filters", + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 200, + "default": 50 + }, + "cursor": { + "type": "string" + }, + "actor": { + "type": "string", + "format": "at-identifier", + "description": "Filter by DID or handle (triggers on-demand backfill)" + }, + "profiles": { + "type": "boolean", + "description": "Include profile + identity info keyed by DID" + }, + "wMin": { + "type": "string", + "description": "Minimum value for w" + }, + "wMax": { + "type": "string", + "description": "Maximum value for w" + }, + "hMin": { + "type": "string", + "description": "Minimum value for h" + }, + "hMax": { + "type": "string", + "description": "Maximum value for h" + }, + "xMin": { + "type": "string", + "description": "Minimum value for x" + }, + "xMax": { + "type": "string", + "description": "Maximum value for x" + }, + "yMin": { + "type": "string", + "description": "Minimum value for y" + }, + "yMax": { + "type": "string", + "description": "Maximum value for y" + }, + "mobileWMin": { + "type": "string", + "description": "Minimum value for mobileW" + }, + "mobileWMax": { + "type": "string", + "description": "Maximum value for mobileW" + }, + "mobileHMin": { + "type": "string", + "description": "Minimum value for mobileH" + }, + "mobileHMax": { + "type": "string", + "description": "Maximum value for mobileH" + }, + "mobileXMin": { + "type": "string", + "description": "Minimum value for mobileX" + }, + "mobileXMax": { + "type": "string", + "description": "Maximum value for mobileX" + }, + "mobileYMin": { + "type": "string", + "description": "Minimum value for mobileY" + }, + "mobileYMax": { + "type": "string", + "description": "Maximum value for mobileY" + }, + "cardType": { + "type": "string", + "description": "Filter by cardType" + }, + "color": { + "type": "string", + "description": "Filter by color" + }, + "page": { + "type": "string", + "description": "Filter by page" + }, + "updatedAtMin": { + "type": "string", + "description": "Minimum value for updatedAt" + }, + "updatedAtMax": { + "type": "string", + "description": "Maximum value for updatedAt" + }, + "versionMin": { + "type": "string", + "description": "Minimum value for version" + }, + "versionMax": { + "type": "string", + "description": "Maximum value for version" + }, + "sort": { + "type": "string", + "knownValues": [ + "w", + "h", + "x", + "y", + "mobileW", + "mobileH", + "mobileX", + "mobileY", + "cardType", + "color", + "page", + "updatedAt", + "version" + ], + "description": "Field to sort by (default: time_us)" + }, + "order": { + "type": "string", + "knownValues": [ + "asc", + "desc" + ], + "description": "Sort direction (default: desc for dates/numbers/counts, asc for strings)" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "records" + ], + "properties": { + "records": { + "type": "array", + "items": { + "type": "ref", + "ref": "#record" + } + }, + "cursor": { + "type": "string" + }, + "profiles": { + "type": "array", + "items": { + "type": "ref", + "ref": "#profileEntry" + } + } + } + } + } + }, + "record": { + "type": "object", + "required": [ + "uri", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "cid": { + "type": "string" + }, + "record": { + "type": "ref", + "ref": "app.blento.card#main" + }, + "time_us": { + "type": "integer" + } + } + }, + "profileEntry": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "cid": { + "type": "string" + }, + "record": { + "type": "unknown" + } + } + } + } +} diff --git a/lexicons-generated/app/blento/getCursor.json b/lexicons-generated/app/blento/getCursor.json new file mode 100644 index 0000000..555c126 --- /dev/null +++ b/lexicons-generated/app/blento/getCursor.json @@ -0,0 +1,27 @@ +{ + "lexicon": 1, + "id": "app.blento.getCursor", + "defs": { + "main": { + "type": "query", + "description": "Get the current cursor position", + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "properties": { + "time_us": { + "type": "integer" + }, + "date": { + "type": "string" + }, + "seconds_ago": { + "type": "integer" + } + } + } + } + } + } +} diff --git a/lexicons-generated/app/blento/getOverview.json b/lexicons-generated/app/blento/getOverview.json new file mode 100644 index 0000000..02050d6 --- /dev/null +++ b/lexicons-generated/app/blento/getOverview.json @@ -0,0 +1,51 @@ +{ + "lexicon": 1, + "id": "app.blento.getOverview", + "defs": { + "main": { + "type": "query", + "description": "Get an overview of all indexed collections", + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "total_records", + "collections" + ], + "properties": { + "total_records": { + "type": "integer" + }, + "collections": { + "type": "array", + "items": { + "type": "ref", + "ref": "#collectionStats" + } + } + } + } + } + }, + "collectionStats": { + "type": "object", + "required": [ + "collection", + "records", + "unique_users" + ], + "properties": { + "collection": { + "type": "string" + }, + "records": { + "type": "integer" + }, + "unique_users": { + "type": "integer" + } + } + } + } +} diff --git a/lexicons-generated/app/blento/getProfile.json b/lexicons-generated/app/blento/getProfile.json new file mode 100644 index 0000000..e1a4ee7 --- /dev/null +++ b/lexicons-generated/app/blento/getProfile.json @@ -0,0 +1,73 @@ +{ + "lexicon": 1, + "id": "app.blento.getProfile", + "defs": { + "main": { + "type": "query", + "description": "Get a user's profiles by DID or handle", + "parameters": { + "type": "params", + "required": [ + "actor" + ], + "properties": { + "actor": { + "type": "string", + "format": "at-identifier", + "description": "DID or handle of the user" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "profiles" + ], + "properties": { + "profiles": { + "type": "array", + "items": { + "type": "ref", + "ref": "#profileEntry" + } + } + } + } + } + }, + "profileEntry": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "cid": { + "type": "string" + }, + "record": { + "type": "unknown" + } + } + } + } +} diff --git a/lexicons-generated/app/blento/notifyOfUpdate.json b/lexicons-generated/app/blento/notifyOfUpdate.json new file mode 100644 index 0000000..08382b7 --- /dev/null +++ b/lexicons-generated/app/blento/notifyOfUpdate.json @@ -0,0 +1,59 @@ +{ + "lexicon": 1, + "id": "app.blento.notifyOfUpdate", + "defs": { + "main": { + "type": "procedure", + "description": "Notify of a record change for immediate indexing. Fetches the record from the user's PDS and indexes (or deletes) it.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "properties": { + "uri": { + "type": "string", + "format": "at-uri", + "description": "Single AT URI to fetch and index" + }, + "uris": { + "type": "array", + "items": { + "type": "string", + "format": "at-uri" + }, + "maxLength": 25, + "description": "Batch of AT URIs to fetch and index (max 25)" + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "indexed", + "deleted" + ], + "properties": { + "indexed": { + "type": "integer", + "description": "Number of records created or updated" + }, + "deleted": { + "type": "integer", + "description": "Number of records deleted (not found on PDS)" + }, + "errors": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Errors for individual URIs that could not be processed" + } + } + } + } + } + } +} diff --git a/lexicons-generated/app/blento/page/getRecord.json b/lexicons-generated/app/blento/page/getRecord.json new file mode 100644 index 0000000..8d5c65f --- /dev/null +++ b/lexicons-generated/app/blento/page/getRecord.json @@ -0,0 +1,106 @@ +{ + "lexicon": 1, + "id": "app.blento.page.getRecord", + "defs": { + "main": { + "type": "query", + "description": "Get a single app.blento.page record by AT URI", + "parameters": { + "type": "params", + "required": [ + "uri" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri", + "description": "AT URI of the record" + }, + "profiles": { + "type": "boolean", + "description": "Include profile + identity info keyed by DID" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "uri", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "cid": { + "type": "string" + }, + "record": { + "type": "ref", + "ref": "app.blento.page#main" + }, + "time_us": { + "type": "integer" + }, + "profiles": { + "type": "array", + "items": { + "type": "ref", + "ref": "#profileEntry" + } + } + } + } + } + }, + "profileEntry": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "cid": { + "type": "string" + }, + "record": { + "type": "unknown" + } + } + } + } +} diff --git a/lexicons-generated/app/blento/page/listRecords.json b/lexicons-generated/app/blento/page/listRecords.json new file mode 100644 index 0000000..fcc5845 --- /dev/null +++ b/lexicons-generated/app/blento/page/listRecords.json @@ -0,0 +1,154 @@ +{ + "lexicon": 1, + "id": "app.blento.page.listRecords", + "defs": { + "main": { + "type": "query", + "description": "Query app.blento.page records with filters", + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 200, + "default": 50 + }, + "cursor": { + "type": "string" + }, + "actor": { + "type": "string", + "format": "at-identifier", + "description": "Filter by DID or handle (triggers on-demand backfill)" + }, + "profiles": { + "type": "boolean", + "description": "Include profile + identity info keyed by DID" + }, + "name": { + "type": "string", + "description": "Filter by name" + }, + "description": { + "type": "string", + "description": "Filter by description" + }, + "sort": { + "type": "string", + "knownValues": [ + "name", + "description" + ], + "description": "Field to sort by (default: time_us)" + }, + "order": { + "type": "string", + "knownValues": [ + "asc", + "desc" + ], + "description": "Sort direction (default: desc for dates/numbers/counts, asc for strings)" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "records" + ], + "properties": { + "records": { + "type": "array", + "items": { + "type": "ref", + "ref": "#record" + } + }, + "cursor": { + "type": "string" + }, + "profiles": { + "type": "array", + "items": { + "type": "ref", + "ref": "#profileEntry" + } + } + } + } + } + }, + "record": { + "type": "object", + "required": [ + "uri", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "cid": { + "type": "string" + }, + "record": { + "type": "ref", + "ref": "app.blento.page#main" + }, + "time_us": { + "type": "integer" + } + } + }, + "profileEntry": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "cid": { + "type": "string" + }, + "record": { + "type": "unknown" + } + } + } + } +} diff --git a/lexicons/app/blento/card.json b/lexicons/app/blento/card.json new file mode 100644 index 0000000..aa9beec --- /dev/null +++ b/lexicons/app/blento/card.json @@ -0,0 +1,31 @@ +{ + "$type": "com.atproto.lexicon.schema", + "lexicon": 1, + "id": "app.blento.card", + "defs": { + "main": { + "type": "record", + "key": "tid", + "record": { + "type": "object", + "required": ["w", "h", "x", "y", "mobileW", "mobileH", "mobileX", "mobileY", "cardType", "cardData"], + "properties": { + "w": { "type": "integer" }, + "h": { "type": "integer" }, + "x": { "type": "integer" }, + "y": { "type": "integer" }, + "mobileW": { "type": "integer" }, + "mobileH": { "type": "integer" }, + "mobileX": { "type": "integer" }, + "mobileY": { "type": "integer" }, + "cardType": { "type": "string" }, + "cardData": { "type": "unknown" }, + "color": { "type": "string" }, + "page": { "type": "string" }, + "updatedAt": { "type": "string", "format": "datetime" }, + "version": { "type": "integer" } + } + } + } + } +} diff --git a/lexicons/app/blento/page.json b/lexicons/app/blento/page.json new file mode 100644 index 0000000..7e23b47 --- /dev/null +++ b/lexicons/app/blento/page.json @@ -0,0 +1,35 @@ +{ + "$type": "com.atproto.lexicon.schema", + "lexicon": 1, + "id": "app.blento.page", + "defs": { + "main": { + "type": "record", + "key": "any", + "record": { + "type": "object", + "properties": { + "url": { "type": "string", "format": "uri" }, + "name": { "type": "string" }, + "description": { "type": "string" }, + "icon": { "type": "blob", "accept": ["image/*"] }, + "preferences": { + "type": "ref", + "ref": "#preferences" + } + } + } + }, + "preferences": { + "type": "object", + "properties": { + "hideProfile": { "type": "boolean" }, + "hideProfileSection": { "type": "boolean" }, + "profilePosition": { "type": "string" }, + "accentColor": { "type": "string" }, + "baseColor": { "type": "string" }, + "editedOn": { "type": "integer" } + } + } + } +} diff --git a/package.json b/package.json index 97bb541..b99d22b 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vite dev", - "build": "NODE_OPTIONS='--max-old-space-size=4096' vite build", + "build": "NODE_OPTIONS='--max-old-space-size=4096' vite build && tsx scripts/append-scheduled.ts", "preview": "pnpm run build && wrangler dev", "prepare": "svelte-kit sync || echo ''", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", @@ -13,9 +13,18 @@ "lint": "prettier --check . && eslint .", "format": "eslint --fix . && prettier --write .", "deploy": "pnpm run build && wrangler deploy", - "cf-typegen": "wrangler types ./src/worker-configuration.d.ts" + "cf-typegen": "wrangler types ./src/worker-configuration.d.ts", + "env:generate-key": "npx tsx src/lib/atproto/scripts/generate-key.ts", + "env:generate-secret": "npx tsx src/lib/atproto/scripts/generate-secret.ts", + "env:setup-dev": "npx tsx src/lib/atproto/scripts/setup-dev.ts", + "tunnel": "npx tsx src/lib/atproto/scripts/tunnel.ts", + "generate": "npx tsx scripts/generate.ts", + "sync": "npx tsx scripts/sync.ts", + "sync:remote": "npx tsx scripts/sync.ts --remote" }, "devDependencies": { + "@atcute/lex-cli": "^2.6.1", + "@atcute/lexicon-doc": "^2.1.2", "@eslint/compat": "^2.0.3", "@eslint/js": "^10.0.1", "@sveltejs/adapter-cloudflare": "^7.2.8", @@ -49,8 +58,10 @@ "@atcute/identity-resolver": "^1.2.2", "@atcute/lexicons": "^1.2.9", "@atcute/oauth-browser-client": "^3.0.0", + "@atcute/oauth-node-client": "^1.1.0", "@atcute/standard-site": "^1.0.1", "@atcute/tid": "^1.1.2", + "@atmo-dev/contrail": "^0.0.8", "@cloudflare/workers-types": "^4.20260313.1", "@ethercorps/sveltekit-og": "^4.2.1", "@floating-ui/dom": "^1.7.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2623af0..c8e6f90 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,12 +32,18 @@ importers: '@atcute/oauth-browser-client': specifier: ^3.0.0 version: 3.0.0(@atcute/identity@1.1.3) + '@atcute/oauth-node-client': + specifier: ^1.1.0 + version: 1.1.0 '@atcute/standard-site': specifier: ^1.0.1 version: 1.0.1 '@atcute/tid': specifier: ^1.1.2 version: 1.1.2 + '@atmo-dev/contrail': + specifier: ^0.0.8 + version: 0.0.8(@atcute/identity@1.1.3)(react@19.2.4) '@cloudflare/workers-types': specifier: ^4.20260313.1 version: 4.20260313.1 @@ -201,6 +207,12 @@ importers: specifier: ^4.73.0 version: 4.73.0(@cloudflare/workers-types@4.20260313.1) devDependencies: + '@atcute/lex-cli': + specifier: ^2.6.1 + version: 2.6.1 + '@atcute/lexicon-doc': + specifier: ^2.1.2 + version: 2.1.2 '@eslint/compat': specifier: ^2.0.3 version: 2.0.3(eslint@10.0.3(jiti@2.6.1)) @@ -285,9 +297,21 @@ packages: '@atcute/bluesky@3.3.0': resolution: {integrity: sha512-TrLnlxuD6F/D2ZYzJ3aCiRD0yiFuhmVsd6oULNzzr8V9Xzlufg0yxkRiGmbMiF2iI508y/MFi6vzo625301c5A==} + '@atcute/car@5.1.1': + resolution: {integrity: sha512-MeRUJNXYgAHrJZw7mMoZJb9xIqv3LZLQw90rRRAVAo8SGNdICwyqe6Bf2LGesX73QM04MBuYO6Kqhvold3TFfg==} + + '@atcute/cbor@2.3.2': + resolution: {integrity: sha512-xP2SORSau/VVI00x2V4BjwIkHr6EQ7l/MXEOPaa4LGYtePFc4gnD4L1yN10dT5NEuUnvGEuCh6arLB7gz1smVQ==} + + '@atcute/cid@2.4.1': + resolution: {integrity: sha512-bwhna69RCv7yetXudtj+2qrMPYvhhIQqvJz6YUpUS98v7OdF3X2dnye9Nig2NDrklZcuyOsu7sQo7GOykJXRLQ==} + '@atcute/client@4.2.1': resolution: {integrity: sha512-ZBFM2pW075JtgGFu5g7HHZBecrClhlcNH8GVP9Zz1aViWR+cjjBsTpeE63rJs+FCOHFYlirUyo5L8SGZ4kMINw==} + '@atcute/crypto@2.4.1': + resolution: {integrity: sha512-tJ3Pi/XYcAsABKtqSlSOTKfO5YiQ4XdqlTuPS8HiRZSezOPcXBFFzAFWpSIJPURbVPFQL3LLrrK0Ea24wl5qeQ==} + '@atcute/identity-resolver@1.2.2': resolution: {integrity: sha512-eUh/UH4bFvuXS0X7epYCeJC/kj4rbBXfSRumLEH4smMVwNOgTo7cL/0Srty+P/qVPoZEyXdfEbS0PHJyzoXmHw==} peerDependencies: @@ -296,12 +320,40 @@ packages: '@atcute/identity@1.1.3': resolution: {integrity: sha512-oIqPoI8TwWeQxvcLmFEZLdN2XdWcaLVtlm8pNk0E72As9HNzzD9pwKPrLr3rmTLRIoULPPFmq9iFNsTeCIU9ng==} + '@atcute/identity@1.1.4': + resolution: {integrity: sha512-RCw1IqflfuSYCxK5m0lZCm0UnvIzcUnuhngiBhJEJb9a9Mc2SEf1xP3H8N5r8pvEH1LoAYd6/zrvCNU+uy9esw==} + + '@atcute/jetstream@1.1.2': + resolution: {integrity: sha512-u6p/h2xppp7LE6W/9xErAJ6frfN60s8adZuCKtfAaaBBiiYbb1CfpzN8Uc+2qtJZNorqGvuuDb5572Jmh7yHBQ==} + + '@atcute/lex-cli@2.6.1': + resolution: {integrity: sha512-eSF2/ANOfegrDsbOi4iLbdCsubCBtRigoyShOix0wBCm1TVc7L7QsTgkZAy8Tet+spT1zPVVysPLlywUzMYSYw==} + hasBin: true + + '@atcute/lexicon-doc@2.1.2': + resolution: {integrity: sha512-jTLcOka7b8BIn2SnIZm2m7l6unlJ0gpgW1MnRpSqNbly/AvyRUR/GREduh/QmjT4SGasDm8vdhrM0kOSPFpDLQ==} + + '@atcute/lexicon-resolver@0.1.6': + resolution: {integrity: sha512-wJC/ChmpP7k+ywpOd07CMvioXjIGaFpF3bDwXLi/086LYjSWHOvtW6pyC+mqP5wLhjyH2hn4wmi77Buew1l1aw==} + peerDependencies: + '@atcute/identity': ^1.1.0 + '@atcute/identity-resolver': ^1.1.3 + + '@atcute/lexicons@1.2.10': + resolution: {integrity: sha512-0EfRDQQjOgb06VSFOUWXLnqKY11ljWB2bXS3cJVPYJp0jTWudgRp6OTW4vReNAeVZaY4kVr2ud/I/Zn9mjix3g==} + '@atcute/lexicons@1.2.9': resolution: {integrity: sha512-/RRHm2Cw9o8Mcsrq0eo8fjS9okKYLGfuFwrQ0YoP/6sdSDsXshaTLJsvLlcUcaDaSJ1YFOuHIo3zr2Om2F/16g==} + '@atcute/mst@1.0.0': + resolution: {integrity: sha512-pMce2efib+dmKtnGnIvJZitVncJkpr3AmhyfgfYllni8KzsaDGsJmuGavSVpuojAhQe+6jYwHFtpm/beiiH4uw==} + '@atcute/multibase@1.1.8': resolution: {integrity: sha512-pJgtImMZKCjqwRbu+2GzB+4xQjKBXDwdZOzeqe0u97zYKRGftpGYGvYv3+pMe2xXe+msDyu7Nv8iJp+U14otTA==} + '@atcute/multibase@1.2.0': + resolution: {integrity: sha512-ZK2GRra+qIYq9nNuQB52m2ul0hOmCQEtPobGfTSUxm7pF0OGEkWGkWHugFhNEDVzHzTwPxHp6VGotdZFue4lYQ==} + '@atcute/oauth-browser-client@3.0.0': resolution: {integrity: sha512-7AbKV8tTe7aRJNJV7gCcWHSVEADb2nr58O1p7dQsf73HSe9pvlBkj/Vk1yjjtH691uAVYkwhHSh0bC7D8XdwJw==} @@ -311,9 +363,15 @@ packages: '@atcute/oauth-keyset@0.1.0': resolution: {integrity: sha512-+wqT/+I5Lg9VzKnKY3g88+N45xbq+wsdT6bHDGqCVa2u57gRvolFF4dY+weMfc/OX641BIZO6/o+zFtKBsMQnQ==} + '@atcute/oauth-node-client@1.1.0': + resolution: {integrity: sha512-xCp/VfjtvTeKscKR/oI2hdMTp1/DaF/7ll8b6yZOCgbKlVDDfhCn5mmKNVARGTNaoywxrXG3XffbWCIx3/E87w==} + '@atcute/oauth-types@0.1.1': resolution: {integrity: sha512-u+3KMjse3Uc/9hDyilu1QVN7IpcnjVXgRzhddzBB8Uh6wePHNVBDdi9wQvFTVVA3zmxtMJVptXRyLLg6Ou9bqg==} + '@atcute/repo@0.1.4': + resolution: {integrity: sha512-uzbGJkE+1A8UFviosJrtw7HW87u8nCCH1V3yOQ79FPrRhS67EvEHF6GTg4aMkP21ze/pRtttJ1k9pFfDmyTlTg==} + '@atcute/standard-site@1.0.1': resolution: {integrity: sha512-wL4ZAvbe3p7NxC92rRgc6vbd+0feNDEAfEcBLA+68KDTUtmtEko5lr09R31P7AWlN4MVTMRj5iLb9UaTThIzWw==} @@ -332,6 +390,20 @@ packages: '@atcute/util-text@1.1.1': resolution: {integrity: sha512-JH0SxzUQJAmbOBTYyhxQbkkI6M33YpjlVLEcbP5GYt43xgFArzV0FJVmEpvIj0kjsmphHB45b6IitdvxPdec9w==} + '@atcute/util-text@1.2.0': + resolution: {integrity: sha512-b8WSh+Z7K601eUFFmTFj8QPKDO8Ic0VDDj63sdKzpkm+ySQKsYT5nXekViGqFVKbyKj1V5FyvZvgXad6/aI4QQ==} + + '@atcute/varint@2.0.0': + resolution: {integrity: sha512-CEY/oVK/nVpL4e5y3sdenLETDL6/Xu5xsE/0TupK+f0Yv8jcD60t2gD8SHROWSvUwYLdkjczLCSA7YrtnjCzWw==} + + '@atmo-dev/contrail@0.0.8': + resolution: {integrity: sha512-sXtdd3Z8VNVoSinrX3ww978ctxtHBl0bX5tP51XOY0IWKJ4xl9zqkPOIIBMzbVE3IyU2Vq2B9Whi3VAhyd2Qdg==} + peerDependencies: + pg: ^8.0.0 + peerDependenciesMeta: + pg: + optional: true + '@badrap/valita@0.4.6': resolution: {integrity: sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg==} engines: {node: '>= 18'} @@ -869,17 +941,34 @@ packages: '@maplibre/vt-pbf@4.3.0': resolution: {integrity: sha512-jIvp8F5hQCcreqOOpEt42TJMUlsrEcpf/kI1T2v85YrQRV6PPXUcEXUg5karKtH6oh47XJZ4kHu56pUkOuqA7w==} + '@mary-ext/event-iterator@1.0.0': + resolution: {integrity: sha512-l6gCPsWJ8aRCe/s7/oCmero70kDHgIK5m4uJvYgwEYTqVxoBOIXbKr5tnkLqUHEg6mNduB4IWvms3h70Hp9ADQ==} + + '@mary-ext/simple-event-emitter@1.0.1': + resolution: {integrity: sha512-9+VvZisxZ/gSg+JJH7hmXaA8Qj42Qjz3O58RSB+INYc8iLA0icATZxHB9vKbj59ojDGZjO3hCKzMXocx3L0H8w==} + '@mixmark-io/domino@2.2.0': resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==} '@napi-rs/wasm-runtime@1.1.1': resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} + '@noble/secp256k1@3.0.0': + resolution: {integrity: sha512-NJBaR352KyIvj3t6sgT/+7xrNyF9Xk9QlLSIqUGVUYlsnDTAUqY8LOmwpcgEx4AMJXRITQ5XEVHD+mMaPfr3mg==} + '@number-flow/svelte@0.4.0': resolution: {integrity: sha512-9tnowrlZlBV3IVe3Gm1V7yXSf4Ugag2k7iW45xqb04HXSa1ApEImopvGWAjJpHDvS849o+UCb0YH461Mtde9lA==} peerDependencies: svelte: ^4 || ^5 + '@optique/core@0.10.7': + resolution: {integrity: sha512-FwSX8ILFqzcCqZi6Xetsa4flJp/yyqFG4d4eFD98BtqdzxxuylzdrKvsXj/ow8mcoVjYkTuaIkqHSBxonqMcQg==} + engines: {bun: '>=1.2.0', deno: '>=2.3.0', node: '>=20.0.0'} + + '@optique/run@0.10.7': + resolution: {integrity: sha512-1CVdH8uyptj1nFGS2MLacSmZceRClez4LD/G/Gm38wrAVnJq6I+9Fvyh2bVHErsZLQzR0a12CYMUWIgDKY3X1w==} + engines: {bun: '>=1.2.0', deno: '>=2.3.0', node: '>=20.0.0'} + '@oxc-project/runtime@0.115.0': resolution: {integrity: sha512-Rg8Wlt5dCbXhQnsXPrkOjL1DTSvXLgb2R/KYfnf1/K+R0k6UMLEmbQXPM+kwrWqSmWA2t0B1EtHy2/3zikQpvQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1845,6 +1934,9 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + event-target-polyfill@0.0.4: + resolution: {integrity: sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ==} + exsolve@1.0.8: resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} @@ -1927,6 +2019,10 @@ packages: hls.js@1.6.15: resolution: {integrity: sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA==} + hono@4.12.12: + resolution: {integrity: sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==} + engines: {node: '>=16.9.0'} + htmlparser2@10.1.0: resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==} @@ -2314,6 +2410,14 @@ packages: parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + partysocket@1.1.16: + resolution: {integrity: sha512-d7xFv+ZC7x0p/DAHWJ5FhxQhimIx+ucyZY+kxL0cKddLBmK9c4p2tEA/L+dOOrWm6EYrRwrBjKQV0uSzOY9x1w==} + peerDependencies: + react: '>=17' + peerDependenciesMeta: + react: + optional: true + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2850,6 +2954,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + typescript-eslint@8.57.0: resolution: {integrity: sha512-W8GcigEMEeB07xEZol8oJ26rigm3+bfPHxHvwbYUlu1fUDsGuQ7Hiskx5xGW/xM4USc9Ephe3jtv7ZYPQntHeA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -3042,6 +3150,10 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yocto-queue@1.2.2: + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} + yoga-wasm-web@0.3.3: resolution: {integrity: sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==} @@ -3069,11 +3181,35 @@ snapshots: '@atcute/atproto': 3.1.10 '@atcute/lexicons': 1.2.9 + '@atcute/car@5.1.1': + dependencies: + '@atcute/cbor': 2.3.2 + '@atcute/cid': 2.4.1 + '@atcute/uint8array': 1.1.1 + '@atcute/varint': 2.0.0 + + '@atcute/cbor@2.3.2': + dependencies: + '@atcute/cid': 2.4.1 + '@atcute/multibase': 1.1.8 + '@atcute/uint8array': 1.1.1 + + '@atcute/cid@2.4.1': + dependencies: + '@atcute/multibase': 1.1.8 + '@atcute/uint8array': 1.1.1 + '@atcute/client@4.2.1': dependencies: '@atcute/identity': 1.1.3 '@atcute/lexicons': 1.2.9 + '@atcute/crypto@2.4.1': + dependencies: + '@atcute/multibase': 1.2.0 + '@atcute/uint8array': 1.1.1 + '@noble/secp256k1': 3.0.0 + '@atcute/identity-resolver@1.2.2(@atcute/identity@1.1.3)': dependencies: '@atcute/identity': 1.1.3 @@ -3081,11 +3217,74 @@ snapshots: '@atcute/util-fetch': 1.0.5 '@badrap/valita': 0.4.6 + '@atcute/identity-resolver@1.2.2(@atcute/identity@1.1.4)': + dependencies: + '@atcute/identity': 1.1.4 + '@atcute/lexicons': 1.2.9 + '@atcute/util-fetch': 1.0.5 + '@badrap/valita': 0.4.6 + '@atcute/identity@1.1.3': dependencies: '@atcute/lexicons': 1.2.9 '@badrap/valita': 0.4.6 + '@atcute/identity@1.1.4': + dependencies: + '@atcute/lexicons': 1.2.10 + '@badrap/valita': 0.4.6 + + '@atcute/jetstream@1.1.2(react@19.2.4)': + dependencies: + '@atcute/lexicons': 1.2.10 + '@badrap/valita': 0.4.6 + '@mary-ext/event-iterator': 1.0.0 + '@mary-ext/simple-event-emitter': 1.0.1 + partysocket: 1.1.16(react@19.2.4) + type-fest: 4.41.0 + yocto-queue: 1.2.2 + transitivePeerDependencies: + - react + + '@atcute/lex-cli@2.6.1': + dependencies: + '@atcute/identity': 1.1.4 + '@atcute/identity-resolver': 1.2.2(@atcute/identity@1.1.4) + '@atcute/lexicon-doc': 2.1.2 + '@atcute/lexicon-resolver': 0.1.6(@atcute/identity-resolver@1.2.2(@atcute/identity@1.1.3))(@atcute/identity@1.1.4) + '@atcute/lexicons': 1.2.10 + '@badrap/valita': 0.4.6 + '@optique/core': 0.10.7 + '@optique/run': 0.10.7 + picocolors: 1.1.1 + prettier: 3.8.1 + + '@atcute/lexicon-doc@2.1.2': + dependencies: + '@atcute/identity': 1.1.3 + '@atcute/lexicons': 1.2.9 + '@atcute/uint8array': 1.1.1 + '@atcute/util-text': 1.1.1 + '@badrap/valita': 0.4.6 + + '@atcute/lexicon-resolver@0.1.6(@atcute/identity-resolver@1.2.2(@atcute/identity@1.1.3))(@atcute/identity@1.1.4)': + dependencies: + '@atcute/crypto': 2.4.1 + '@atcute/identity': 1.1.4 + '@atcute/identity-resolver': 1.2.2(@atcute/identity@1.1.3) + '@atcute/lexicon-doc': 2.1.2 + '@atcute/lexicons': 1.2.10 + '@atcute/repo': 0.1.4 + '@atcute/util-fetch': 1.0.5 + '@badrap/valita': 0.4.6 + + '@atcute/lexicons@1.2.10': + dependencies: + '@atcute/uint8array': 1.1.1 + '@atcute/util-text': 1.2.0 + '@standard-schema/spec': 1.1.0 + esm-env: 1.2.2 + '@atcute/lexicons@1.2.9': dependencies: '@atcute/uint8array': 1.1.1 @@ -3093,10 +3292,20 @@ snapshots: '@standard-schema/spec': 1.1.0 esm-env: 1.2.2 + '@atcute/mst@1.0.0': + dependencies: + '@atcute/cbor': 2.3.2 + '@atcute/cid': 2.4.1 + '@atcute/uint8array': 1.1.1 + '@atcute/multibase@1.1.8': dependencies: '@atcute/uint8array': 1.1.1 + '@atcute/multibase@1.2.0': + dependencies: + '@atcute/uint8array': 1.1.1 + '@atcute/oauth-browser-client@3.0.0(@atcute/identity@1.1.3)': dependencies: '@atcute/client': 4.2.1 @@ -3120,6 +3329,19 @@ snapshots: dependencies: '@atcute/oauth-crypto': 0.1.0 + '@atcute/oauth-node-client@1.1.0': + dependencies: + '@atcute/client': 4.2.1 + '@atcute/identity': 1.1.3 + '@atcute/identity-resolver': 1.2.2(@atcute/identity@1.1.3) + '@atcute/lexicons': 1.2.9 + '@atcute/oauth-crypto': 0.1.0 + '@atcute/oauth-keyset': 0.1.0 + '@atcute/oauth-types': 0.1.1 + '@atcute/util-fetch': 1.0.5 + '@badrap/valita': 0.4.6 + nanoid: 5.1.6 + '@atcute/oauth-types@0.1.1': dependencies: '@atcute/identity': 1.1.3 @@ -3127,6 +3349,16 @@ snapshots: '@atcute/oauth-keyset': 0.1.0 '@badrap/valita': 0.4.6 + '@atcute/repo@0.1.4': + dependencies: + '@atcute/car': 5.1.1 + '@atcute/cbor': 2.3.2 + '@atcute/cid': 2.4.1 + '@atcute/crypto': 2.4.1 + '@atcute/lexicons': 1.2.10 + '@atcute/mst': 1.0.0 + '@atcute/uint8array': 1.1.1 + '@atcute/standard-site@1.0.1': dependencies: '@atcute/atproto': 3.1.10 @@ -3148,6 +3380,24 @@ snapshots: dependencies: unicode-segmenter: 0.14.5 + '@atcute/util-text@1.2.0': + dependencies: + unicode-segmenter: 0.14.5 + + '@atcute/varint@2.0.0': {} + + '@atmo-dev/contrail@0.0.8(@atcute/identity@1.1.3)(react@19.2.4)': + dependencies: + '@atcute/atproto': 3.1.10 + '@atcute/client': 4.2.1 + '@atcute/identity-resolver': 1.2.2(@atcute/identity@1.1.3) + '@atcute/jetstream': 1.1.2(react@19.2.4) + '@atcute/lexicons': 1.2.10 + hono: 4.12.12 + transitivePeerDependencies: + - '@atcute/identity' + - react + '@badrap/valita@0.4.6': {} '@cloudflare/kv-asset-handler@0.4.2': {} @@ -3665,6 +3915,12 @@ snapshots: pbf: 4.0.1 supercluster: 8.0.1 + '@mary-ext/event-iterator@1.0.0': + dependencies: + yocto-queue: 1.2.2 + + '@mary-ext/simple-event-emitter@1.0.1': {} + '@mixmark-io/domino@2.2.0': {} '@napi-rs/wasm-runtime@1.1.1': @@ -3674,12 +3930,20 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true + '@noble/secp256k1@3.0.0': {} + '@number-flow/svelte@0.4.0(svelte@5.53.11)': dependencies: esm-env: 1.2.2 number-flow: 0.6.0 svelte: 5.53.11 + '@optique/core@0.10.7': {} + + '@optique/run@0.10.7': + dependencies: + '@optique/core': 0.10.7 + '@oxc-project/runtime@0.115.0': {} '@oxc-project/types@0.115.0': {} @@ -4663,6 +4927,8 @@ snapshots: esutils@2.0.3: {} + event-target-polyfill@0.0.4: {} + exsolve@1.0.8: {} fast-deep-equal@3.1.3: {} @@ -4720,6 +4986,8 @@ snapshots: hls.js@1.6.15: {} + hono@4.12.12: {} + htmlparser2@10.1.0: dependencies: domelementtype: 2.3.0 @@ -5072,6 +5340,12 @@ snapshots: dependencies: entities: 6.0.1 + partysocket@1.1.16(react@19.2.4): + dependencies: + event-target-polyfill: 0.0.4 + optionalDependencies: + react: 19.2.4 + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -5630,6 +5904,8 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-fest@4.41.0: {} + typescript-eslint@8.57.0(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3): dependencies: '@typescript-eslint/eslint-plugin': 8.57.0(@typescript-eslint/parser@8.57.0(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3))(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3) @@ -5764,6 +6040,8 @@ snapshots: yocto-queue@0.1.0: {} + yocto-queue@1.2.2: {} + yoga-wasm-web@0.3.3: {} youch-core@0.3.3: diff --git a/scripts/append-scheduled.ts b/scripts/append-scheduled.ts new file mode 100644 index 0000000..c9bd1cd --- /dev/null +++ b/scripts/append-scheduled.ts @@ -0,0 +1,29 @@ +/** + * Post-build script: appends a `scheduled` handler to the SvelteKit worker output. + * + * SvelteKit's adapter-cloudflare doesn't support the `scheduled` export natively + * (see https://github.com/sveltejs/kit/issues/4841). This script patches the + * generated _worker.js to add one that self-calls the /api/cron endpoint. + */ +import { readFileSync, writeFileSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const root = join(dirname(fileURLToPath(import.meta.url)), '..'); +const workerPath = join(root, '.svelte-kit', 'cloudflare', '_worker.js'); + +let code = readFileSync(workerPath, 'utf-8'); + +code += ` +// --- Appended by scripts/append-scheduled.ts --- +worker_default.scheduled = async function (event, env, ctx) { + const req = new Request('http://localhost/api/cron', { + method: 'POST', + headers: { 'X-Cron-Secret': env.CRON_SECRET || '' } + }); + ctx.waitUntil(this.fetch(req, env, ctx)); +}; +`; + +writeFileSync(workerPath, code); +console.log('Appended scheduled handler to _worker.js'); diff --git a/scripts/generate.ts b/scripts/generate.ts new file mode 100644 index 0000000..4a893d5 --- /dev/null +++ b/scripts/generate.ts @@ -0,0 +1,14 @@ +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; +import { config } from '../src/lib/contrail/config'; +import { generateLexicons } from '@atmo-dev/contrail/generate'; + +const ROOT_DIR = join(dirname(fileURLToPath(import.meta.url)), '..'); + +generateLexicons({ + config, + rootDir: ROOT_DIR, + lexiconDir: join(ROOT_DIR, 'lexicons'), + outputDir: join(ROOT_DIR, 'lexicons-generated'), + writeRuntimeFiles: true +}); diff --git a/scripts/sync.ts b/scripts/sync.ts new file mode 100644 index 0000000..c946f86 --- /dev/null +++ b/scripts/sync.ts @@ -0,0 +1,68 @@ +/** + * Discover users from relays and backfill their records from PDS. + * + * Usage: + * pnpm sync # local D1 + * pnpm sync:remote # prod D1 + */ +import { Contrail } from '@atmo-dev/contrail'; +import { config } from '../src/lib/contrail/config'; +import { getPlatformProxy } from 'wrangler'; + +function elapsed(start: number): string { + const ms = Date.now() - start; + if (ms < 1000) return `${ms}ms`; + if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`; + const mins = Math.floor(ms / 60_000); + const secs = ((ms % 60_000) / 1000).toFixed(0); + return `${mins}m ${secs}s`; +} + +async function main() { + const remote = process.argv.includes('--remote'); + const syncStart = Date.now(); + + console.log(`=== Sync (${remote ? 'remote/prod' : 'local'} D1) ===\n`); + + const { env, dispose } = await getPlatformProxy<{ DB: D1Database }>({ + environment: remote ? 'production' : undefined + }); + + const contrail = new Contrail({ ...config, db: env.DB }); + + try { + await contrail.init(); + + console.log('--- Discovery ---'); + const discoveryStart = Date.now(); + const discovered = await contrail.discover(); + console.log(` Done: ${discovered.length} users in ${elapsed(discoveryStart)}\n`); + + console.log('--- Backfill ---'); + const backfillStart = Date.now(); + const total = await contrail.backfill({ + concurrency: 100, + onProgress: ({ records, usersComplete, usersTotal, usersFailed }) => { + const secs = (Date.now() - backfillStart) / 1000; + const rate = secs > 0 ? Math.round(records / secs) : 0; + const failStr = usersFailed > 0 ? ` | ${usersFailed} failed` : ''; + process.stdout.write( + `\r ${records} records | ${usersComplete}/${usersTotal} users | ${rate}/s | ${elapsed(backfillStart)}${failStr} ` + ); + } + }); + process.stdout.write('\n'); + console.log(` Done: ${total} records in ${elapsed(backfillStart)}\n`); + + console.log(`=== Finished in ${elapsed(syncStart)} ===`); + console.log(` Discovered: ${discovered.length} users`); + console.log(` Backfilled: ${total} records`); + } finally { + await dispose(); + } +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/src/app.d.ts b/src/app.d.ts index eea418d..3cf574a 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,17 +1,30 @@ -import { KVNamespace } from '@cloudflare/workers-types'; +import type { KVNamespace, D1Database } from '@cloudflare/workers-types'; +import type { OAuthSession } from '@atcute/oauth-node-client'; +import type { Client } from '@atcute/client'; +import type { Did } from '@atcute/lexicons'; // See https://svelte.dev/docs/kit/types#app.d.ts // for information about these interfaces declare global { namespace App { // interface Error {} - // interface Locals {} + interface Locals { + session: OAuthSession | null; + client: Client | null; + did: Did | null; + } // interface PageData {} // interface PageState {} interface Platform { env: { USER_DATA_CACHE: KVNamespace; CUSTOM_DOMAINS: KVNamespace; + OAUTH_SESSIONS: KVNamespace; + OAUTH_STATES: KVNamespace; + DB: D1Database; + CLIENT_ASSERTION_KEY: string; + COOKIE_SECRET: string; + CRON_SECRET: string; }; } } @@ -19,5 +32,6 @@ declare global { import type {} from '@atcute/atproto'; import type {} from '@atcute/bluesky'; +import type {} from './lexicon-types'; export {}; diff --git a/src/hooks.server.ts b/src/hooks.server.ts new file mode 100644 index 0000000..d86f244 --- /dev/null +++ b/src/hooks.server.ts @@ -0,0 +1,18 @@ +import type { Handle } from '@sveltejs/kit'; +import { restoreSession } from '$lib/atproto/server/session'; + +export const handle: Handle = async ({ event, resolve }) => { + const customDomain = event.request.headers.get('X-Custom-Domain')?.toLowerCase() || undefined; + + const { session, client, did } = await restoreSession( + event.cookies, + event.platform?.env, + customDomain + ); + + event.locals.session = session; + event.locals.client = client; + event.locals.did = did; + + return resolve(event); +}; diff --git a/src/lexicon-types/index.ts b/src/lexicon-types/index.ts new file mode 100644 index 0000000..5f06e64 --- /dev/null +++ b/src/lexicon-types/index.ts @@ -0,0 +1,10 @@ +export * as AppBlentoCard from "./types/app/blento/card.js"; +export * as AppBlentoCardGetRecord from "./types/app/blento/card/getRecord.js"; +export * as AppBlentoCardListRecords from "./types/app/blento/card/listRecords.js"; +export * as AppBlentoGetCursor from "./types/app/blento/getCursor.js"; +export * as AppBlentoGetOverview from "./types/app/blento/getOverview.js"; +export * as AppBlentoGetProfile from "./types/app/blento/getProfile.js"; +export * as AppBlentoNotifyOfUpdate from "./types/app/blento/notifyOfUpdate.js"; +export * as AppBlentoPage from "./types/app/blento/page.js"; +export * as AppBlentoPageGetRecord from "./types/app/blento/page/getRecord.js"; +export * as AppBlentoPageListRecords from "./types/app/blento/page/listRecords.js"; diff --git a/src/lexicon-types/types/app/blento/card.ts b/src/lexicon-types/types/app/blento/card.ts new file mode 100644 index 0000000..89f29cc --- /dev/null +++ b/src/lexicon-types/types/app/blento/card.ts @@ -0,0 +1,38 @@ +import type {} from "@atcute/lexicons"; +import * as v from "@atcute/lexicons/validations"; +import type {} from "@atcute/lexicons/ambient"; + +const _mainSchema = /*#__PURE__*/ v.record( + /*#__PURE__*/ v.tidString(), + /*#__PURE__*/ v.object({ + $type: /*#__PURE__*/ v.literal("app.blento.card"), + cardData: /*#__PURE__*/ v.unknown(), + cardType: /*#__PURE__*/ v.string(), + color: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + h: /*#__PURE__*/ v.integer(), + mobileH: /*#__PURE__*/ v.integer(), + mobileW: /*#__PURE__*/ v.integer(), + mobileX: /*#__PURE__*/ v.integer(), + mobileY: /*#__PURE__*/ v.integer(), + page: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + updatedAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), + version: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), + w: /*#__PURE__*/ v.integer(), + x: /*#__PURE__*/ v.integer(), + y: /*#__PURE__*/ v.integer(), + }), +); + +type main$schematype = typeof _mainSchema; + +export interface mainSchema extends main$schematype {} + +export const mainSchema = _mainSchema as mainSchema; + +export interface Main extends v.InferInput {} + +declare module "@atcute/lexicons/ambient" { + interface Records { + "app.blento.card": mainSchema; + } +} diff --git a/src/lexicon-types/types/app/blento/card/getRecord.ts b/src/lexicon-types/types/app/blento/card/getRecord.ts new file mode 100644 index 0000000..04bc2c7 --- /dev/null +++ b/src/lexicon-types/types/app/blento/card/getRecord.ts @@ -0,0 +1,68 @@ +import type {} from "@atcute/lexicons"; +import * as v from "@atcute/lexicons/validations"; +import type {} from "@atcute/lexicons/ambient"; +import * as AppBlentoCard from "../card.js"; + +const _mainSchema = /*#__PURE__*/ v.query("app.blento.card.getRecord", { + params: /*#__PURE__*/ v.object({ + /** + * Include profile + identity info keyed by DID + */ + profiles: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), + /** + * AT URI of the record + */ + uri: /*#__PURE__*/ v.resourceUriString(), + }), + output: { + type: "lex", + schema: /*#__PURE__*/ v.object({ + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + collection: /*#__PURE__*/ v.nsidString(), + did: /*#__PURE__*/ v.didString(), + get profiles() { + return /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.array(profileEntrySchema), + ); + }, + get record() { + return /*#__PURE__*/ v.optional(AppBlentoCard.mainSchema); + }, + rkey: /*#__PURE__*/ v.string(), + time_us: /*#__PURE__*/ v.integer(), + uri: /*#__PURE__*/ v.resourceUriString(), + }), + }, +}); +const _profileEntrySchema = /*#__PURE__*/ v.object({ + $type: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.literal("app.blento.card.getRecord#profileEntry"), + ), + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + collection: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.nsidString()), + did: /*#__PURE__*/ v.didString(), + handle: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + record: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.unknown()), + rkey: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), +}); + +type main$schematype = typeof _mainSchema; +type profileEntry$schematype = typeof _profileEntrySchema; + +export interface mainSchema extends main$schematype {} +export interface profileEntrySchema extends profileEntry$schematype {} + +export const mainSchema = _mainSchema as mainSchema; +export const profileEntrySchema = _profileEntrySchema as profileEntrySchema; + +export interface ProfileEntry extends v.InferInput {} + +export interface $params extends v.InferInput {} +export interface $output extends v.InferXRPCBodyInput {} + +declare module "@atcute/lexicons/ambient" { + interface XRPCQueries { + "app.blento.card.getRecord": mainSchema; + } +} diff --git a/src/lexicon-types/types/app/blento/card/listRecords.ts b/src/lexicon-types/types/app/blento/card/listRecords.ts new file mode 100644 index 0000000..efa75a6 --- /dev/null +++ b/src/lexicon-types/types/app/blento/card/listRecords.ts @@ -0,0 +1,212 @@ +import type {} from "@atcute/lexicons"; +import * as v from "@atcute/lexicons/validations"; +import type {} from "@atcute/lexicons/ambient"; +import * as AppBlentoCard from "../card.js"; + +const _mainSchema = /*#__PURE__*/ v.query("app.blento.card.listRecords", { + params: /*#__PURE__*/ v.object({ + /** + * Filter by DID or handle (triggers on-demand backfill) + */ + actor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.actorIdentifierString()), + /** + * Filter by cardType + */ + cardType: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Filter by color + */ + color: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Maximum value for h + */ + hMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Minimum value for h + */ + hMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * @minimum 1 + * @maximum 200 + * @default 50 + */ + limit: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.integer(), [ + /*#__PURE__*/ v.integerRange(1, 200), + ]), + 50, + ), + /** + * Maximum value for mobileH + */ + mobileHMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Minimum value for mobileH + */ + mobileHMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Maximum value for mobileW + */ + mobileWMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Minimum value for mobileW + */ + mobileWMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Maximum value for mobileX + */ + mobileXMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Minimum value for mobileX + */ + mobileXMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Maximum value for mobileY + */ + mobileYMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Minimum value for mobileY + */ + mobileYMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Sort direction (default: desc for dates/numbers/counts, asc for strings) + */ + order: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.string<"asc" | "desc" | (string & {})>(), + ), + /** + * Filter by page + */ + page: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Include profile + identity info keyed by DID + */ + profiles: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), + /** + * Field to sort by (default: time_us) + */ + sort: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.string< + | "cardType" + | "color" + | "h" + | "mobileH" + | "mobileW" + | "mobileX" + | "mobileY" + | "page" + | "updatedAt" + | "version" + | "w" + | "x" + | "y" + | (string & {}) + >(), + ), + /** + * Maximum value for updatedAt + */ + updatedAtMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Minimum value for updatedAt + */ + updatedAtMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Maximum value for version + */ + versionMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Minimum value for version + */ + versionMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Maximum value for w + */ + wMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Minimum value for w + */ + wMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Maximum value for x + */ + xMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Minimum value for x + */ + xMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Maximum value for y + */ + yMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Minimum value for y + */ + yMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + }), + output: { + type: "lex", + schema: /*#__PURE__*/ v.object({ + cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + get profiles() { + return /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.array(profileEntrySchema), + ); + }, + get records() { + return /*#__PURE__*/ v.array(recordSchema); + }, + }), + }, +}); +const _profileEntrySchema = /*#__PURE__*/ v.object({ + $type: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.literal("app.blento.card.listRecords#profileEntry"), + ), + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + collection: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.nsidString()), + did: /*#__PURE__*/ v.didString(), + handle: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + record: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.unknown()), + rkey: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), +}); +const _recordSchema = /*#__PURE__*/ v.object({ + $type: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.literal("app.blento.card.listRecords#record"), + ), + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + collection: /*#__PURE__*/ v.nsidString(), + did: /*#__PURE__*/ v.didString(), + get record() { + return /*#__PURE__*/ v.optional(AppBlentoCard.mainSchema); + }, + rkey: /*#__PURE__*/ v.string(), + time_us: /*#__PURE__*/ v.integer(), + uri: /*#__PURE__*/ v.resourceUriString(), +}); + +type main$schematype = typeof _mainSchema; +type profileEntry$schematype = typeof _profileEntrySchema; +type record$schematype = typeof _recordSchema; + +export interface mainSchema extends main$schematype {} +export interface profileEntrySchema extends profileEntry$schematype {} +export interface recordSchema extends record$schematype {} + +export const mainSchema = _mainSchema as mainSchema; +export const profileEntrySchema = _profileEntrySchema as profileEntrySchema; +export const recordSchema = _recordSchema as recordSchema; + +export interface ProfileEntry extends v.InferInput {} +export interface Record extends v.InferInput {} + +export interface $params extends v.InferInput {} +export interface $output extends v.InferXRPCBodyInput {} + +declare module "@atcute/lexicons/ambient" { + interface XRPCQueries { + "app.blento.card.listRecords": mainSchema; + } +} diff --git a/src/lexicon-types/types/app/blento/getCursor.ts b/src/lexicon-types/types/app/blento/getCursor.ts new file mode 100644 index 0000000..ce2b38d --- /dev/null +++ b/src/lexicon-types/types/app/blento/getCursor.ts @@ -0,0 +1,30 @@ +import type {} from "@atcute/lexicons"; +import * as v from "@atcute/lexicons/validations"; +import type {} from "@atcute/lexicons/ambient"; + +const _mainSchema = /*#__PURE__*/ v.query("app.blento.getCursor", { + params: null, + output: { + type: "lex", + schema: /*#__PURE__*/ v.object({ + date: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + seconds_ago: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), + time_us: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), + }), + }, +}); + +type main$schematype = typeof _mainSchema; + +export interface mainSchema extends main$schematype {} + +export const mainSchema = _mainSchema as mainSchema; + +export interface $params {} +export interface $output extends v.InferXRPCBodyInput {} + +declare module "@atcute/lexicons/ambient" { + interface XRPCQueries { + "app.blento.getCursor": mainSchema; + } +} diff --git a/src/lexicon-types/types/app/blento/getOverview.ts b/src/lexicon-types/types/app/blento/getOverview.ts new file mode 100644 index 0000000..11b1bc1 --- /dev/null +++ b/src/lexicon-types/types/app/blento/getOverview.ts @@ -0,0 +1,47 @@ +import type {} from "@atcute/lexicons"; +import * as v from "@atcute/lexicons/validations"; +import type {} from "@atcute/lexicons/ambient"; + +const _collectionStatsSchema = /*#__PURE__*/ v.object({ + $type: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.literal("app.blento.getOverview#collectionStats"), + ), + collection: /*#__PURE__*/ v.string(), + records: /*#__PURE__*/ v.integer(), + unique_users: /*#__PURE__*/ v.integer(), +}); +const _mainSchema = /*#__PURE__*/ v.query("app.blento.getOverview", { + params: null, + output: { + type: "lex", + schema: /*#__PURE__*/ v.object({ + get collections() { + return /*#__PURE__*/ v.array(collectionStatsSchema); + }, + total_records: /*#__PURE__*/ v.integer(), + }), + }, +}); + +type collectionStats$schematype = typeof _collectionStatsSchema; +type main$schematype = typeof _mainSchema; + +export interface collectionStatsSchema extends collectionStats$schematype {} +export interface mainSchema extends main$schematype {} + +export const collectionStatsSchema = + _collectionStatsSchema as collectionStatsSchema; +export const mainSchema = _mainSchema as mainSchema; + +export interface CollectionStats extends v.InferInput< + typeof collectionStatsSchema +> {} + +export interface $params {} +export interface $output extends v.InferXRPCBodyInput {} + +declare module "@atcute/lexicons/ambient" { + interface XRPCQueries { + "app.blento.getOverview": mainSchema; + } +} diff --git a/src/lexicon-types/types/app/blento/getProfile.ts b/src/lexicon-types/types/app/blento/getProfile.ts new file mode 100644 index 0000000..58e78aa --- /dev/null +++ b/src/lexicon-types/types/app/blento/getProfile.ts @@ -0,0 +1,52 @@ +import type {} from "@atcute/lexicons"; +import * as v from "@atcute/lexicons/validations"; +import type {} from "@atcute/lexicons/ambient"; + +const _mainSchema = /*#__PURE__*/ v.query("app.blento.getProfile", { + params: /*#__PURE__*/ v.object({ + /** + * DID or handle of the user + */ + actor: /*#__PURE__*/ v.actorIdentifierString(), + }), + output: { + type: "lex", + schema: /*#__PURE__*/ v.object({ + get profiles() { + return /*#__PURE__*/ v.array(profileEntrySchema); + }, + }), + }, +}); +const _profileEntrySchema = /*#__PURE__*/ v.object({ + $type: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.literal("app.blento.getProfile#profileEntry"), + ), + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + collection: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.nsidString()), + did: /*#__PURE__*/ v.didString(), + handle: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + record: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.unknown()), + rkey: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), +}); + +type main$schematype = typeof _mainSchema; +type profileEntry$schematype = typeof _profileEntrySchema; + +export interface mainSchema extends main$schematype {} +export interface profileEntrySchema extends profileEntry$schematype {} + +export const mainSchema = _mainSchema as mainSchema; +export const profileEntrySchema = _profileEntrySchema as profileEntrySchema; + +export interface ProfileEntry extends v.InferInput {} + +export interface $params extends v.InferInput {} +export interface $output extends v.InferXRPCBodyInput {} + +declare module "@atcute/lexicons/ambient" { + interface XRPCQueries { + "app.blento.getProfile": mainSchema; + } +} diff --git a/src/lexicon-types/types/app/blento/notifyOfUpdate.ts b/src/lexicon-types/types/app/blento/notifyOfUpdate.ts new file mode 100644 index 0000000..488901a --- /dev/null +++ b/src/lexicon-types/types/app/blento/notifyOfUpdate.ts @@ -0,0 +1,61 @@ +import type {} from "@atcute/lexicons"; +import * as v from "@atcute/lexicons/validations"; +import type {} from "@atcute/lexicons/ambient"; + +const _mainSchema = /*#__PURE__*/ v.procedure("app.blento.notifyOfUpdate", { + params: null, + input: { + type: "lex", + schema: /*#__PURE__*/ v.object({ + /** + * Single AT URI to fetch and index + */ + uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), + /** + * Batch of AT URIs to fetch and index (max 25) + * @maxLength 25 + */ + uris: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.constrain( + /*#__PURE__*/ v.array(/*#__PURE__*/ v.resourceUriString()), + [/*#__PURE__*/ v.arrayLength(0, 25)], + ), + ), + }), + }, + output: { + type: "lex", + schema: /*#__PURE__*/ v.object({ + /** + * Number of records deleted (not found on PDS) + */ + deleted: /*#__PURE__*/ v.integer(), + /** + * Errors for individual URIs that could not be processed + */ + errors: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.array(/*#__PURE__*/ v.string()), + ), + /** + * Number of records created or updated + */ + indexed: /*#__PURE__*/ v.integer(), + }), + }, +}); + +type main$schematype = typeof _mainSchema; + +export interface mainSchema extends main$schematype {} + +export const mainSchema = _mainSchema as mainSchema; + +export interface $params {} +export interface $input extends v.InferXRPCBodyInput {} +export interface $output extends v.InferXRPCBodyInput {} + +declare module "@atcute/lexicons/ambient" { + interface XRPCProcedures { + "app.blento.notifyOfUpdate": mainSchema; + } +} diff --git a/src/lexicon-types/types/app/blento/page.ts b/src/lexicon-types/types/app/blento/page.ts new file mode 100644 index 0000000..2fff7db --- /dev/null +++ b/src/lexicon-types/types/app/blento/page.ts @@ -0,0 +1,49 @@ +import type {} from "@atcute/lexicons"; +import * as v from "@atcute/lexicons/validations"; +import type {} from "@atcute/lexicons/ambient"; + +const _mainSchema = /*#__PURE__*/ v.record( + /*#__PURE__*/ v.string(), + /*#__PURE__*/ v.object({ + $type: /*#__PURE__*/ v.literal("app.blento.page"), + description: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * @accept image/* + */ + icon: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.blob()), + name: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + get preferences() { + return /*#__PURE__*/ v.optional(preferencesSchema); + }, + url: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.genericUriString()), + }), +); +const _preferencesSchema = /*#__PURE__*/ v.object({ + $type: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.literal("app.blento.page#preferences"), + ), + accentColor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + baseColor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + editedOn: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), + hideProfile: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), + hideProfileSection: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), + profilePosition: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), +}); + +type main$schematype = typeof _mainSchema; +type preferences$schematype = typeof _preferencesSchema; + +export interface mainSchema extends main$schematype {} +export interface preferencesSchema extends preferences$schematype {} + +export const mainSchema = _mainSchema as mainSchema; +export const preferencesSchema = _preferencesSchema as preferencesSchema; + +export interface Main extends v.InferInput {} +export interface Preferences extends v.InferInput {} + +declare module "@atcute/lexicons/ambient" { + interface Records { + "app.blento.page": mainSchema; + } +} diff --git a/src/lexicon-types/types/app/blento/page/getRecord.ts b/src/lexicon-types/types/app/blento/page/getRecord.ts new file mode 100644 index 0000000..95f5fec --- /dev/null +++ b/src/lexicon-types/types/app/blento/page/getRecord.ts @@ -0,0 +1,68 @@ +import type {} from "@atcute/lexicons"; +import * as v from "@atcute/lexicons/validations"; +import type {} from "@atcute/lexicons/ambient"; +import * as AppBlentoPage from "../page.js"; + +const _mainSchema = /*#__PURE__*/ v.query("app.blento.page.getRecord", { + params: /*#__PURE__*/ v.object({ + /** + * Include profile + identity info keyed by DID + */ + profiles: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), + /** + * AT URI of the record + */ + uri: /*#__PURE__*/ v.resourceUriString(), + }), + output: { + type: "lex", + schema: /*#__PURE__*/ v.object({ + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + collection: /*#__PURE__*/ v.nsidString(), + did: /*#__PURE__*/ v.didString(), + get profiles() { + return /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.array(profileEntrySchema), + ); + }, + get record() { + return /*#__PURE__*/ v.optional(AppBlentoPage.mainSchema); + }, + rkey: /*#__PURE__*/ v.string(), + time_us: /*#__PURE__*/ v.integer(), + uri: /*#__PURE__*/ v.resourceUriString(), + }), + }, +}); +const _profileEntrySchema = /*#__PURE__*/ v.object({ + $type: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.literal("app.blento.page.getRecord#profileEntry"), + ), + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + collection: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.nsidString()), + did: /*#__PURE__*/ v.didString(), + handle: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + record: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.unknown()), + rkey: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), +}); + +type main$schematype = typeof _mainSchema; +type profileEntry$schematype = typeof _profileEntrySchema; + +export interface mainSchema extends main$schematype {} +export interface profileEntrySchema extends profileEntry$schematype {} + +export const mainSchema = _mainSchema as mainSchema; +export const profileEntrySchema = _profileEntrySchema as profileEntrySchema; + +export interface ProfileEntry extends v.InferInput {} + +export interface $params extends v.InferInput {} +export interface $output extends v.InferXRPCBodyInput {} + +declare module "@atcute/lexicons/ambient" { + interface XRPCQueries { + "app.blento.page.getRecord": mainSchema; + } +} diff --git a/src/lexicon-types/types/app/blento/page/listRecords.ts b/src/lexicon-types/types/app/blento/page/listRecords.ts new file mode 100644 index 0000000..b21fdb5 --- /dev/null +++ b/src/lexicon-types/types/app/blento/page/listRecords.ts @@ -0,0 +1,113 @@ +import type {} from "@atcute/lexicons"; +import * as v from "@atcute/lexicons/validations"; +import type {} from "@atcute/lexicons/ambient"; +import * as AppBlentoPage from "../page.js"; + +const _mainSchema = /*#__PURE__*/ v.query("app.blento.page.listRecords", { + params: /*#__PURE__*/ v.object({ + /** + * Filter by DID or handle (triggers on-demand backfill) + */ + actor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.actorIdentifierString()), + cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Filter by description + */ + description: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * @minimum 1 + * @maximum 200 + * @default 50 + */ + limit: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.integer(), [ + /*#__PURE__*/ v.integerRange(1, 200), + ]), + 50, + ), + /** + * Filter by name + */ + name: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Sort direction (default: desc for dates/numbers/counts, asc for strings) + */ + order: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.string<"asc" | "desc" | (string & {})>(), + ), + /** + * Include profile + identity info keyed by DID + */ + profiles: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), + /** + * Field to sort by (default: time_us) + */ + sort: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.string<"description" | "name" | (string & {})>(), + ), + }), + output: { + type: "lex", + schema: /*#__PURE__*/ v.object({ + cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + get profiles() { + return /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.array(profileEntrySchema), + ); + }, + get records() { + return /*#__PURE__*/ v.array(recordSchema); + }, + }), + }, +}); +const _profileEntrySchema = /*#__PURE__*/ v.object({ + $type: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.literal("app.blento.page.listRecords#profileEntry"), + ), + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + collection: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.nsidString()), + did: /*#__PURE__*/ v.didString(), + handle: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + record: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.unknown()), + rkey: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), +}); +const _recordSchema = /*#__PURE__*/ v.object({ + $type: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.literal("app.blento.page.listRecords#record"), + ), + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + collection: /*#__PURE__*/ v.nsidString(), + did: /*#__PURE__*/ v.didString(), + get record() { + return /*#__PURE__*/ v.optional(AppBlentoPage.mainSchema); + }, + rkey: /*#__PURE__*/ v.string(), + time_us: /*#__PURE__*/ v.integer(), + uri: /*#__PURE__*/ v.resourceUriString(), +}); + +type main$schematype = typeof _mainSchema; +type profileEntry$schematype = typeof _profileEntrySchema; +type record$schematype = typeof _recordSchema; + +export interface mainSchema extends main$schematype {} +export interface profileEntrySchema extends profileEntry$schematype {} +export interface recordSchema extends record$schematype {} + +export const mainSchema = _mainSchema as mainSchema; +export const profileEntrySchema = _profileEntrySchema as profileEntrySchema; +export const recordSchema = _recordSchema as recordSchema; + +export interface ProfileEntry extends v.InferInput {} +export interface Record extends v.InferInput {} + +export interface $params extends v.InferInput {} +export interface $output extends v.InferXRPCBodyInput {} + +declare module "@atcute/lexicons/ambient" { + interface XRPCQueries { + "app.blento.page.listRecords": mainSchema; + } +} diff --git a/src/lib/atproto/auth.svelte.ts b/src/lib/atproto/auth.svelte.ts index 0ddaf39..7bf66bd 100644 --- a/src/lib/atproto/auth.svelte.ts +++ b/src/lib/atproto/auth.svelte.ts @@ -1,254 +1,94 @@ -import { - configureOAuth, - createAuthorizationUrl, - finalizeAuthorization, - OAuthUserAgent, - getSession, - deleteStoredSession -} from '@atcute/oauth-browser-client'; -import { AppBskyActorDefs } from '@atcute/bluesky'; -import { - CompositeDidDocumentResolver, - CompositeHandleResolver, - DohJsonHandleResolver, - LocalActorResolver, - PlcDidDocumentResolver, - WebDidDocumentResolver, - WellKnownHandleResolver -} from '@atcute/identity-resolver'; -import { Client } from '@atcute/client'; - -import { dev } from '$app/environment'; -import { replaceState } from '$app/navigation'; - -import { metadata } from './metadata'; -import { describeRepo, getDetailedProfile } from './methods'; -import { DOH_RESOLVER, REDIRECT_PATH, signUpPDS } from './settings'; -import { SvelteURLSearchParams } from 'svelte/reactivity'; - +import { type AppBskyActorDefs } from '@atcute/bluesky'; import type { ActorIdentifier, Did } from '@atcute/lexicons'; +import { page } from '$app/state'; -export const user = $state({ - agent: null as OAuthUserAgent | null, - client: null as Client | null, - profile: null as AppBskyActorDefs.ProfileViewDetailed | null | undefined, - isInitializing: true, - isLoggedIn: false, - did: undefined as Did | undefined -}); - -export async function initClient(options?: { customDomain?: string }) { - user.isInitializing = true; +let cachedProfile = $state(null); +let cachedDid = $state(null); - let client_id = dev - ? `http://localhost` + - `?redirect_uri=${encodeURIComponent('http://127.0.0.1:5179' + REDIRECT_PATH)}` + - `&scope=${encodeURIComponent(metadata.scope)}` - : metadata.client_id; +// Load profile client-side when authDid changes +$effect.root(() => { + $effect(() => { + const did = page.data?.authDid as Did | undefined; - const handleResolver = new CompositeHandleResolver({ - methods: { - dns: new DohJsonHandleResolver({ dohUrl: DOH_RESOLVER }), - http: new WellKnownHandleResolver() + if (!did) { + cachedProfile = null; + cachedDid = null; + return; } - }); - let redirect_uri = dev ? 'http://127.0.0.1:5179' + REDIRECT_PATH : metadata.redirect_uris[0]; + if (did === cachedDid && cachedProfile) return; - if (options?.customDomain) { - client_id = client_id.replace('blento.app', options.customDomain); - redirect_uri = redirect_uri.replace('blento.app', options.customDomain); + cachedDid = did; - console.log(client_id, redirect_uri); - } else { - console.log('no custom domain'); - } + // If the current page already has this user's profile (e.g. viewing own page), use it + if (page.data?.profile?.did === did) { + cachedProfile = page.data.profile; + return; + } - configureOAuth({ - metadata: { - client_id, - redirect_uri - }, - identityResolver: new LocalActorResolver({ - handleResolver: handleResolver, - didDocumentResolver: new CompositeDidDocumentResolver({ - methods: { - plc: new PlcDidDocumentResolver(), - web: new WebDidDocumentResolver() - } - }) - }) + // Otherwise fetch it client-side + import('@atcute/client').then(({ Client, simpleFetchHandler }) => { + const client = new Client({ + handler: simpleFetchHandler({ service: 'https://public.api.bsky.app' }) + }); + client + .get('app.bsky.actor.getProfile', { params: { actor: did } }) + .then((res) => { + if (res.ok && cachedDid === did) { + cachedProfile = res.data; + } + }) + .catch(() => {}); + }); }); +}); - const params = new SvelteURLSearchParams(location.hash.slice(1)); - - const did = (localStorage.getItem('current-login') as Did) ?? undefined; - - if (params.size > 0) { - await finalizeLogin(params, did); - } else if (did) { - await resumeSession(did); +export const user: { + profile: AppBskyActorDefs.ProfileViewDetailed | null | undefined; + isLoggedIn: boolean; + did: Did | undefined; +} = { + get profile() { + return cachedProfile; + }, + get isLoggedIn() { + return !!page.data?.authDid; + }, + get did() { + return page.data?.authDid ?? undefined; } - - user.isInitializing = false; -} +}; export async function login(handle: ActorIdentifier) { - console.log('login in with', handle); + const { oauthLogin } = await import('./server/oauth.remote'); + + const returnTo = location.pathname + location.search; + if (handle.startsWith('did:')) { if (handle.length < 6) throw new Error('DID must be at least 6 characters'); - - await startAuthorization(handle as ActorIdentifier); } else if (handle.includes('.') && handle.length > 3) { - const processed = handle.startsWith('@') ? handle.slice(1) : handle; - if (processed.length < 4) throw new Error('Handle must be at least 4 characters'); - - await startAuthorization(processed as ActorIdentifier); + handle = (handle.startsWith('@') ? handle.slice(1) : handle) as ActorIdentifier; } else if (handle.length > 3) { - const processed = (handle.startsWith('@') ? handle.slice(1) : handle) + '.bsky.social'; - await startAuthorization(processed as ActorIdentifier); + handle = ((handle.startsWith('@') ? handle.slice(1) : handle) + + '.bsky.social') as ActorIdentifier; } else { throw new Error('Please provide a valid handle or DID.'); } -} -export async function signup() { - await startAuthorization(); + const { url } = await oauthLogin({ handle, returnTo }); + window.location.assign(url); } -async function startAuthorization(identity?: ActorIdentifier) { - const authUrl = await createAuthorizationUrl({ - target: identity - ? { type: 'account', identifier: identity } - : { type: 'pds', serviceUrl: signUpPDS }, - // @ts-expect-error - new stuff - prompt: identity ? undefined : 'create', - scope: metadata.scope - }); - - localStorage.setItem('login-redirect', location.pathname + location.search); - - // let browser persist local storage - await new Promise((resolve) => setTimeout(resolve, 200)); - - window.location.assign(authUrl); - - await new Promise((_resolve, reject) => { - const listener = () => { - reject(new Error(`user aborted the login request`)); - }; +export async function signup() { + const { oauthLogin } = await import('./server/oauth.remote'); - window.addEventListener('pageshow', listener, { once: true }); - }); + const returnTo = location.pathname + location.search; + const { url } = await oauthLogin({ signup: true, returnTo }); + window.location.assign(url); } export async function logout() { - const currentAgent = user.agent; - if (currentAgent) { - const did = currentAgent.session.info.sub; - - localStorage.removeItem('current-login'); - localStorage.removeItem(`profile-${did}`); - - try { - await currentAgent.signOut(); - } catch { - deleteStoredSession(did); - } - - user.agent = null; - user.profile = null; - user.isLoggedIn = false; - } else { - console.error('trying to logout, but user not signed in'); - return false; - } -} - -async function finalizeLogin(params: SvelteURLSearchParams, did?: Did) { - try { - const { session } = await finalizeAuthorization(params); - replaceState(location.pathname + location.search, {}); - - user.agent = new OAuthUserAgent(session); - user.did = session.info.sub; - user.client = new Client({ handler: user.agent }); - - localStorage.setItem('current-login', session.info.sub); - - await loadProfile(session.info.sub); - - user.isLoggedIn = true; - - try { - if (!user.profile) return; - const recentLogins = JSON.parse(localStorage.getItem('recent-logins') || '{}'); - - recentLogins[session.info.sub] = user.profile; - - localStorage.setItem('recent-logins', JSON.stringify(recentLogins)); - } catch { - console.log('failed to save to recent logins'); - } - } catch (error) { - console.error('error finalizing login', error); - if (did) { - await resumeSession(did); - } - } -} - -async function resumeSession(did: Did) { - try { - const session = await getSession(did); - - if (session.token.expires_at && session.token.expires_at < Date.now()) { - throw Error('session expired'); - } - - const requestedScopes = metadata.scope.split(' ').filter((s) => !s.startsWith('include:')); - const tokenScopes = new Set(session.token.scope?.split(' ')); - if (!requestedScopes.every((s) => tokenScopes.has(s))) { - throw Error('scope changed, signing out!'); - } - - user.agent = new OAuthUserAgent(session); - user.did = session.info.sub; - user.client = new Client({ handler: user.agent }); - - await loadProfile(session.info.sub); - - user.isLoggedIn = true; - } catch (error) { - console.error('error resuming session', error); - deleteStoredSession(did); - } -} - -async function loadProfile(actor: Did) { - // check if profile is already loaded in local storage - const profile = localStorage.getItem(`profile-${actor}`); - if (profile) { - try { - user.profile = JSON.parse(profile); - return; - } catch { - console.error('error loading profile from local storage'); - } - } - - const response = await getDetailedProfile(); - - if (!response || response.handle === 'handle.invalid') { - console.log('invalid handle or no profile from bsky, fetching from repo description'); - const repo = await describeRepo({ did: actor }); - user.profile = { - did: actor, - handle: repo?.handle || 'handle.invalid' - }; - localStorage.setItem(`profile-${actor}`, JSON.stringify(user.profile)); - } else { - user.profile = response; - localStorage.setItem(`profile-${actor}`, JSON.stringify(response)); - } + const { oauthLogout } = await import('./server/oauth.remote'); + await oauthLogout(); + window.location.href = '/'; } diff --git a/src/lib/atproto/index.ts b/src/lib/atproto/index.ts index 1957d6b..cda2c95 100644 --- a/src/lib/atproto/index.ts +++ b/src/lib/atproto/index.ts @@ -1,5 +1,4 @@ -export { user, login, signup, logout, initClient } from './auth.svelte'; -export { metadata } from './metadata'; +export { user, login, signup, logout } from './auth.svelte'; export { parseUri, diff --git a/src/lib/atproto/metadata.ts b/src/lib/atproto/metadata.ts deleted file mode 100644 index 8aa83f4..0000000 --- a/src/lib/atproto/metadata.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { resolve } from '$app/paths'; -import { permissions, REDIRECT_PATH, SITE } from './settings'; - -function constructScope() { - const repos = permissions.collections.map((collection) => 'repo:' + collection).join(' '); - - let rpcs = ''; - for (const [key, value] of Object.entries(permissions.rpc ?? {})) { - if (Array.isArray(value)) { - rpcs += value.map((lxm) => 'rpc?lxm=' + lxm + '&aud=' + key).join(' '); - } else { - rpcs += 'rpc?lxm=' + value + '&aud=' + key; - } - } - - let blobScope: string | undefined = undefined; - if (Array.isArray(permissions.blobs) && permissions.blobs.length > 0) { - blobScope = 'blob?' + permissions.blobs.map((b) => 'accept=' + b).join('&'); - } else if (permissions.blobs && permissions.blobs.length > 0) { - blobScope = 'blob:' + permissions.blobs; - } - - const scope = [ - 'atproto', - repos, - rpcs, - blobScope, - 'include:app.bsky.authCreatePosts include:site.standard.authFull' - ] - .filter((v) => v?.trim()) - .join(' '); - return scope; -} - -export const metadata = { - client_id: SITE + resolve('/oauth-client-metadata.json'), - redirect_uris: [SITE + resolve(REDIRECT_PATH)], - scope: constructScope(), - grant_types: ['authorization_code', 'refresh_token'], - response_types: ['code'], - token_endpoint_auth_method: 'none', - application_type: 'web', - dpop_bound_access_tokens: true -}; diff --git a/src/lib/atproto/methods.ts b/src/lib/atproto/methods.ts index 7c524cb..93e9ba6 100644 --- a/src/lib/atproto/methods.ts +++ b/src/lib/atproto/methods.ts @@ -58,9 +58,6 @@ const didResolver = new CompositeDidDocumentResolver({ /** * Gets the PDS (Personal Data Server) URL for a given DID. - * @param did - The DID to look up - * @returns The PDS service endpoint URL - * @throws If no PDS is found in the DID document */ export async function getPDS(did: Did) { const doc = await didResolver.resolve(did as Did<'plc'> | Did<'web'>); @@ -74,10 +71,6 @@ export async function getPDS(did: Did) { /** * Fetches a detailed Bluesky profile for a user. - * @param data - Optional object with did and client - * @param data.did - The DID to fetch the profile for (defaults to current user) - * @param data.client - The client to use (defaults to public Bluesky API) - * @returns The profile data or undefined if not found */ export async function getDetailedProfile(data?: { did?: Did; client?: Client }) { data ??= {}; @@ -109,7 +102,6 @@ export async function getBlentoOrBskyProfile(data: { did: Did; client?: Client } > { let blentoProfile; try { - // try getting blento profile first blentoProfile = await getRecord({ collection: 'site.standard.publication', did: data?.did, @@ -117,7 +109,7 @@ export async function getBlentoOrBskyProfile(data: { did: Did; client?: Client } client: data?.client }); } catch { - console.error('error getting blento profile, falling back to bsky profile'); + // User doesn't have a blento publication — expected for most users } let response; @@ -143,9 +135,6 @@ export async function getBlentoOrBskyProfile(data: { did: Did; client?: Client } /** * Creates an AT Protocol client for a user's PDS. - * @param did - The DID of the user - * @returns A client configured for the user's PDS - * @throws If the PDS cannot be found */ export async function getClient({ did }: { did: Did }) { const pds = await getPDS(did); @@ -160,12 +149,6 @@ export async function getClient({ did }: { did: Did }) { /** * Lists records from a repository collection with pagination support. - * @param did - The DID of the repository (defaults to current user) - * @param collection - The collection to list records from - * @param cursor - Pagination cursor for continuing from a previous request - * @param limit - Maximum number of records to return (default 100, set to 0 for all records) - * @param client - The client to use (defaults to user's PDS client) - * @returns An array of records from the collection */ export async function listRecords({ did, @@ -216,11 +199,6 @@ export async function listRecords({ /** * Fetches a single record from a repository. - * @param did - The DID of the repository (defaults to current user) - * @param collection - The collection the record belongs to - * @param rkey - The record key (defaults to "self") - * @param client - The client to use (defaults to user's PDS client) - * @returns The record data */ export async function getRecord({ did, @@ -259,13 +237,7 @@ export async function getRecord({ } /** - * Creates or updates a record in the current user's repository. - * Only accepts collections that are configured in permissions. - * @param collection - The collection to write to (must be in permissions.collections) - * @param rkey - The record key (defaults to "self") - * @param record - The record data to write - * @returns The response from the PDS - * @throws If the user is not logged in + * Creates or updates a record in the current user's repository via server-side proxy. */ export async function putRecord({ collection, @@ -276,29 +248,12 @@ export async function putRecord({ rkey?: string; record: Record; }) { - if (!user.client || !user.did) throw new Error('No rpc or did'); - - const response = await user.client.post('com.atproto.repo.putRecord', { - input: { - collection, - repo: user.did, - rkey, - record: { - ...record - } - } - }); - - return response; + const { putRecord: serverPutRecord } = await import('./server/repo.remote'); + return serverPutRecord({ collection, rkey, record }); } /** - * Deletes a record from the current user's repository. - * Only accepts collections that are configured in permissions. - * @param collection - The collection the record belongs to (must be in permissions.collections) - * @param rkey - The record key (defaults to "self") - * @returns True if the deletion was successful - * @throws If the user is not logged in + * Deletes a record from the current user's repository via server-side proxy. */ export async function deleteRecord({ collection, @@ -307,54 +262,22 @@ export async function deleteRecord({ collection: AllowedCollection; rkey: string; }) { - if (!user.client || !user.did) throw new Error('No profile or rpc or did'); - - const response = await user.client.post('com.atproto.repo.deleteRecord', { - input: { - collection, - repo: user.did, - rkey - } - }); - - return response.ok; + const { deleteRecord: serverDeleteRecord } = await import('./server/repo.remote'); + const result = await serverDeleteRecord({ collection, rkey }); + return result.ok; } /** - * Uploads a blob to the current user's PDS. - * @param blob - The blob data to upload - * @returns The blob metadata including ref, mimeType, and size, or undefined on failure - * @throws If the user is not logged in + * Uploads a blob to the current user's PDS via server-side proxy. */ export async function uploadBlob({ blob }: { blob: Blob }) { - if (!user.did || !user.client) throw new Error("Can't upload blob: Not logged in"); - - const blobResponse = await user.client.post('com.atproto.repo.uploadBlob', { - params: { - repo: user.did - }, - input: blob - }); - - if (!blobResponse?.ok) return; - - const blobInfo = blobResponse?.data.blob as { - $type: 'blob'; - ref: { - $link: string; - }; - mimeType: string; - size: number; - }; - - return blobInfo; + const { uploadBlob: serverUploadBlob } = await import('./server/repo.remote'); + const bytes = Array.from(new Uint8Array(await blob.arrayBuffer())); + return serverUploadBlob({ bytes, mimeType: blob.type }); } /** * Gets metadata about a repository. - * @param client - The client to use - * @param did - The DID of the repository (defaults to current user) - * @returns Repository metadata or undefined on failure */ export async function describeRepo({ client, did }: { client?: Client; did?: Did }) { did ??= user.did; @@ -375,9 +298,6 @@ export async function describeRepo({ client, did }: { client?: Client; did?: Did /** * Constructs a URL to fetch a blob directly from a user's PDS. - * @param did - The DID of the user who owns the blob - * @param blob - The blob reference object - * @returns The URL to fetch the blob */ export async function getBlobURL({ did, @@ -397,9 +317,6 @@ export async function getBlobURL({ /** * Constructs a Bluesky CDN URL for an image blob. - * @param did - The DID of the user who owns the blob (defaults to current user) - * @param blob - The blob reference object - * @returns The CDN URL for the image in webp format */ export function getCDNImageBlobUrl({ did, @@ -423,10 +340,6 @@ export function getCDNImageBlobUrl({ /** * Searches for actors with typeahead/autocomplete functionality. - * @param q - The search query - * @param limit - Maximum number of results (default 10) - * @param host - The API host to use (defaults to public Bluesky API) - * @returns An object containing matching actors and the original query */ export async function searchActorsTypeahead( q: string, @@ -453,8 +366,6 @@ export async function searchActorsTypeahead( /** * Return a TID based on current time - * - * @returns TID for current time */ export function createTID() { return TID.now(); @@ -492,9 +403,6 @@ export async function getAuthorFeed(data?: { /** * Fetches posts by their AT URIs. - * @param uris - Array of AT URIs (e.g., "at://did:plc:xyz/app.bsky.feed.post/abc123") - * @param client - The client to use (defaults to public Bluesky API) - * @returns Array of posts or undefined on failure */ export async function getPosts(data: { uris: string[]; client?: Client }) { data.client ??= new Client({ @@ -520,10 +428,6 @@ export function getHandleOrDid(profile: AppBskyActorDefs.ProfileViewDetailed): A /** * Fetches a post's thread including replies. - * @param uri - The AT URI of the post - * @param depth - How many levels of replies to fetch (default 1) - * @param client - The client to use (defaults to public Bluesky API) - * @returns The thread data or undefined on failure */ export async function getPostThread({ uri, @@ -548,11 +452,7 @@ export async function getPostThread({ } /** - * Creates a Bluesky post on the authenticated user's account. - * @param text - The post text - * @param facets - Optional rich text facets (links, mentions, etc.) - * @returns The response containing the post's URI and CID - * @throws If the user is not logged in + * Creates a Bluesky post on the authenticated user's account via server-side proxy. */ export async function createPost({ text, @@ -564,8 +464,6 @@ export async function createPost({ features: Array<{ $type: string; uri?: string; did?: string; tag?: string }>; }>; }) { - if (!user.client || !user.did) throw new Error('No client or did'); - const record: Record = { $type: 'app.bsky.feed.post', text, @@ -576,13 +474,6 @@ export async function createPost({ record.facets = facets; } - const response = await user.client.post('com.atproto.repo.createRecord', { - input: { - collection: 'app.bsky.feed.post', - repo: user.did, - record - } - }); - - return response; + const { createRecord } = await import('./server/repo.remote'); + return createRecord({ collection: 'app.bsky.feed.post', record }); } diff --git a/src/lib/atproto/scripts/generate-key.ts b/src/lib/atproto/scripts/generate-key.ts new file mode 100644 index 0000000..3d1cc8a --- /dev/null +++ b/src/lib/atproto/scripts/generate-key.ts @@ -0,0 +1,4 @@ +import { generateClientAssertionKey } from '@atcute/oauth-node-client'; + +const key = await generateClientAssertionKey('main-key'); +console.log(JSON.stringify(key)); diff --git a/src/lib/atproto/scripts/generate-secret.ts b/src/lib/atproto/scripts/generate-secret.ts new file mode 100644 index 0000000..9092066 --- /dev/null +++ b/src/lib/atproto/scripts/generate-secret.ts @@ -0,0 +1,3 @@ +import { randomBytes } from 'node:crypto'; + +console.log(randomBytes(32).toString('base64url')); diff --git a/src/lib/atproto/scripts/setup-dev.ts b/src/lib/atproto/scripts/setup-dev.ts new file mode 100644 index 0000000..24cd144 --- /dev/null +++ b/src/lib/atproto/scripts/setup-dev.ts @@ -0,0 +1,47 @@ +import { existsSync } from 'node:fs'; +import { copyFile, readFile, writeFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import { randomBytes } from 'node:crypto'; + +import { generateClientAssertionKey } from '@atcute/oauth-node-client'; + +const cwd = process.cwd(); +const examplePath = resolve(cwd, '.env.example'); +const envPath = resolve(cwd, '.env'); + +if (!existsSync(envPath)) { + if (!existsSync(examplePath)) { + throw new Error(`missing .env.example (expected at ${examplePath})`); + } + await copyFile(examplePath, envPath); + console.log(`created ${envPath}`); +} + +const upsertVar = (input: string, key: string, value: string): string => { + const line = `${key}=${value}`; + const re = new RegExp(`^${key}=.*$`, 'm'); + + if (re.test(input)) { + const match = input.match(re); + const current = match ? match[0].slice(key.length + 1).trim() : ''; + // Only overwrite if empty/placeholder + if (current === '' || current === "''" || current === '""' || current.includes('...')) { + return input.replace(re, line); + } + return input; + } + + const suffix = input.endsWith('\n') || input.length === 0 ? '' : '\n'; + return `${input}${suffix}${line}\n`; +}; + +let vars = await readFile(envPath, 'utf8'); + +const secret = randomBytes(32).toString('base64url'); +vars = upsertVar(vars, 'COOKIE_SECRET', secret); + +const jwk = await generateClientAssertionKey('main-key'); +vars = upsertVar(vars, 'CLIENT_ASSERTION_KEY', JSON.stringify(jwk)); + +await writeFile(envPath, vars); +console.log(`updated ${envPath}`); diff --git a/src/lib/atproto/scripts/tunnel.ts b/src/lib/atproto/scripts/tunnel.ts new file mode 100644 index 0000000..4d96652 --- /dev/null +++ b/src/lib/atproto/scripts/tunnel.ts @@ -0,0 +1,176 @@ +import { readFileSync, writeFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { spawn } from 'node:child_process'; + +const DEV_PORT = 5179; + +const cwd = process.cwd(); +const envPath = resolve(cwd, '.env'); +const vitePath = resolve(cwd, 'vite.config.ts'); + +let tunnelUrl: string | null = null; +let statusBarActive = false; + +// ── ANSI status bar ────────────────────────────────────────────── +function getColumns(): number { + return process.stdout.columns || 80; +} + +function getRows(): number { + return process.stdout.rows || 24; +} + +function setupScrollRegion(): void { + if (!process.stdout.isTTY) return; + statusBarActive = true; + const rows = getRows(); + process.stdout.write(`\x1b[1;${rows - 1}r`); + process.stdout.write(`\x1b[${rows - 1};1H`); +} + +function drawStatusBar(text: string): void { + if (!process.stdout.isTTY) { + process.stdout.write(text + '\n'); + return; + } + const rows = getRows(); + const cols = getColumns(); + process.stdout.write('\x1b7'); + process.stdout.write(`\x1b[${rows};1H`); + process.stdout.write('\x1b[2K'); + process.stdout.write(`\x1b[7m ${text.padEnd(cols - 1)}\x1b[0m`); + process.stdout.write('\x1b8'); +} + +function clearStatusBar(): void { + if (!process.stdout.isTTY || !statusBarActive) return; + const rows = getRows(); + process.stdout.write(`\x1b[1;${rows}r`); + process.stdout.write(`\x1b[${rows};1H\x1b[2K`); + process.stdout.write(`\x1b[${rows - 1};1H`); + statusBarActive = false; +} + +function writeLog(text: string): void { + process.stdout.write(text); +} + +process.stdout.on('resize', () => { + if (!statusBarActive || !tunnelUrl) return; + setupScrollRegion(); + drawStatusBar(`Tunnel: ${tunnelUrl} | Ctrl+C to stop`); +}); + +// ── .env helpers ───────────────────────────────────────────────── +function readEnv(): string { + return readFileSync(envPath, 'utf8'); +} + +function writeEnv(content: string): void { + writeFileSync(envPath, content); +} + +function setEnvVar(key: string, value: string): void { + let env = readEnv(); + const re = new RegExp(`^(#\\s*)?${key}=.*$`, 'm'); + const line = `${key}=${value}`; + + if (re.test(env)) { + env = env.replace(re, line); + } else { + env = env.trimEnd() + '\n' + line + '\n'; + } + writeEnv(env); +} + +function clearEnvVar(key: string): void { + let env = readEnv(); + const re = new RegExp(`^${key}=.*$`, 'm'); + + if (re.test(env)) { + env = env.replace(re, `# ${key}=`); + writeEnv(env); + } +} + +// ── vite config helpers ────────────────────────────────────────── +function setViteAllowedHosts(hostname: string): void { + let vite = readFileSync(vitePath, 'utf8'); + + if (/allowedHosts\s*:/.test(vite)) { + vite = vite.replace(/allowedHosts\s*:\s*\[.*?\]/s, `allowedHosts: ['${hostname}']`); + } else if (/server\s*:\s*\{/.test(vite)) { + vite = vite.replace(/server\s*:\s*\{/, `server: {\n\t\tallowedHosts: ['${hostname}'],`); + } + + writeFileSync(vitePath, vite); +} + +function clearViteAllowedHosts(): void { + let vite = readFileSync(vitePath, 'utf8'); + + if (/allowedHosts\s*:/.test(vite)) { + vite = vite.replace(/allowedHosts\s*:\s*\[.*?\]/s, 'allowedHosts: []'); + } + + writeFileSync(vitePath, vite); +} + +// ── cleanup ────────────────────────────────────────────────────── +function cleanup(): void { + clearStatusBar(); + console.log('\nCleaning up...'); + if (tunnelUrl) { + clearEnvVar('OAUTH_PUBLIC_URL'); + console.log(' Cleared OAUTH_PUBLIC_URL from .env'); + clearViteAllowedHosts(); + console.log(' Cleared allowedHosts from vite.config.ts'); + } +} + +// ── main ───────────────────────────────────────────────────────── +const child = spawn('cloudflared', ['tunnel', '--url', `http://localhost:${DEV_PORT}`], { + stdio: ['ignore', 'pipe', 'pipe'] +}); + +child.stderr.on('data', (data: Buffer) => { + const output = data.toString(); + + if (!tunnelUrl) { + const match = output.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/); + if (match) { + tunnelUrl = match[0]; + const hostname = new URL(tunnelUrl).hostname; + + setEnvVar('OAUTH_PUBLIC_URL', tunnelUrl); + setViteAllowedHosts(hostname); + + writeLog(`\n Set OAUTH_PUBLIC_URL=${tunnelUrl}\n`); + writeLog(` Set vite allowedHosts to [${hostname}]\n`); + writeLog(` Tunnel is ready! Restart your dev server to pick up the new URL.\n\n`); + + setupScrollRegion(); + drawStatusBar(`Tunnel: ${tunnelUrl} | Ctrl+C to stop`); + return; + } + } + + writeLog(output); +}); + +child.stdout.on('data', (data: Buffer) => { + writeLog(data.toString()); +}); + +child.on('close', (code) => { + cleanup(); + process.exit(code ?? 0); +}); + +process.on('SIGINT', () => { + child.kill('SIGINT'); +}); + +process.on('SIGTERM', () => { + child.kill('SIGTERM'); +}); diff --git a/src/lib/atproto/server/kv-store.ts b/src/lib/atproto/server/kv-store.ts new file mode 100644 index 0000000..733a020 --- /dev/null +++ b/src/lib/atproto/server/kv-store.ts @@ -0,0 +1,39 @@ +import type { KVNamespace } from '@cloudflare/workers-types'; +import type { Store } from '@atcute/oauth-node-client'; + +export class KVStore implements Store { + private kv: KVNamespace; + private expirationTtl?: number; + + constructor(kv: KVNamespace, options?: { expirationTtl?: number }) { + this.kv = kv; + this.expirationTtl = options?.expirationTtl; + } + + async get(key: K): Promise { + const value = await this.kv.get(key, 'text'); + if (value === null) return undefined; + return JSON.parse(value) as V; + } + + async set(key: K, value: V): Promise { + await this.kv.put(key, JSON.stringify(value), { + expirationTtl: this.expirationTtl + }); + } + + async delete(key: K): Promise { + await this.kv.delete(key); + } + + async clear(): Promise { + let cursor: string | undefined; + do { + const result = await this.kv.list({ cursor }); + for (const key of result.keys) { + await this.kv.delete(key.name); + } + cursor = result.list_complete ? undefined : result.cursor; + } while (cursor); + } +} diff --git a/src/lib/atproto/server/oauth.remote.ts b/src/lib/atproto/server/oauth.remote.ts new file mode 100644 index 0000000..98efab2 --- /dev/null +++ b/src/lib/atproto/server/oauth.remote.ts @@ -0,0 +1,72 @@ +import * as v from 'valibot'; +import { error } from '@sveltejs/kit'; +import { command, getRequestEvent } from '$app/server'; +import { createOAuthClient } from './oauth'; +import { getSignedCookie } from './signed-cookie'; +import { signUpPDS } from '../settings'; +import { scopes } from './scopes'; +import type { ActorIdentifier, Did } from '@atcute/lexicons'; + +function getDomain(): string | undefined { + const { request } = getRequestEvent(); + return request.headers.get('X-Custom-Domain')?.toLowerCase() || undefined; +} + +export const oauthLogin = command( + v.object({ + handle: v.optional(v.pipe(v.string(), v.minLength(3))), + signup: v.optional(v.boolean()), + returnTo: v.optional(v.string()) + }), + async (input) => { + const { platform, cookies } = getRequestEvent(); + + try { + const oauth = createOAuthClient(platform?.env, getDomain()); + + const target = input.signup + ? ({ type: 'pds', serviceUrl: signUpPDS } as const) + : ({ type: 'account', identifier: input.handle as ActorIdentifier } as const); + + const { url } = await oauth.authorize({ + target, + scope: scopes.join(' '), + prompt: input.signup ? 'create' : undefined + }); + + // Store return path in a cookie so the callback can redirect back + if (input.returnTo) { + cookies.set('oauth_return_to', encodeURIComponent(input.returnTo), { + path: '/', + httpOnly: true, + maxAge: 600 // 10 minutes + }); + } + + return { url: url.toString() }; + } catch (e) { + if (e && typeof e === 'object' && 'status' in e) throw e; + const message = e instanceof Error ? e.message : 'Login failed'; + error(400, message); + } + } +); + +export const oauthLogout = command(async () => { + const { cookies, platform } = getRequestEvent(); + const did = getSignedCookie(cookies, 'did') as Did | null; + + if (did) { + try { + const oauth = createOAuthClient(platform?.env, getDomain()); + await oauth.revoke(did); + } catch (e) { + console.error('Error revoking session:', e); + } + } + + cookies.delete('did', { path: '/' }); + cookies.delete('scope', { path: '/' }); + + return { ok: true }; +}); diff --git a/src/lib/atproto/server/oauth.ts b/src/lib/atproto/server/oauth.ts new file mode 100644 index 0000000..e67c6f4 --- /dev/null +++ b/src/lib/atproto/server/oauth.ts @@ -0,0 +1,107 @@ +import { + OAuthClient, + MemoryStore, + type ClientAssertionPrivateJwk, + type OAuthClientStores, + type OAuthSession, + type StoredSession, + type StoredState +} from '@atcute/oauth-node-client'; +import type { Did } from '@atcute/lexicons'; +import { + CompositeDidDocumentResolver, + CompositeHandleResolver, + DohJsonHandleResolver, + LocalActorResolver, + PlcDidDocumentResolver, + WebDidDocumentResolver, + WellKnownHandleResolver +} from '@atcute/identity-resolver'; +import { KVStore } from './kv-store'; +import { DOH_RESOLVER, REDIRECT_PATH, SITE } from '../settings'; +import { scopes } from './scopes'; +import { dev } from '$app/environment'; + +const DEV_PORT = 5179; + +function createActorResolver() { + return new LocalActorResolver({ + handleResolver: new CompositeHandleResolver({ + methods: { + dns: new DohJsonHandleResolver({ dohUrl: DOH_RESOLVER }), + http: new WellKnownHandleResolver() + } + }), + didDocumentResolver: new CompositeDidDocumentResolver({ + methods: { + plc: new PlcDidDocumentResolver(), + web: new WebDidDocumentResolver() + } + }) + }); +} + +function createStores(env?: App.Platform['env']): OAuthClientStores { + if (env?.OAUTH_SESSIONS && env?.OAUTH_STATES) { + return { + sessions: new KVStore(env.OAUTH_SESSIONS), + states: new KVStore(env.OAUTH_STATES, { expirationTtl: 600 }) + }; + } + // Fallback to in-memory stores (dev without wrangler) + return { + sessions: new MemoryStore(), + states: new MemoryStore({ ttl: 600_000 }) + }; +} + +// Cache OAuth clients per domain to avoid re-creating them on every request +const clientCache = new Map(); + +export function createOAuthClient(env?: App.Platform['env'], domain?: string): OAuthClient { + const cacheKey = domain ?? '__default__'; + const cached = clientCache.get(cacheKey); + if (cached) return cached; + + const actorResolver = createActorResolver(); + const stores = createStores(env); + + if (dev && !env?.CLIENT_ASSERTION_KEY) { + // Dev without secrets: loopback public client (no keyset). + const client = new OAuthClient({ + metadata: { + redirect_uris: [`http://127.0.0.1:${DEV_PORT}${REDIRECT_PATH}`], + scope: scopes + }, + actorResolver, + stores + }); + clientCache.set(cacheKey, client); + return client; + } + + // Confidential client (production, or dev with secrets) + if (!env?.CLIENT_ASSERTION_KEY) { + throw new Error('CLIENT_ASSERTION_KEY secret is not set'); + } + + const site = domain ? `https://${domain}` : (SITE ?? 'https://blento.app'); + const key: ClientAssertionPrivateJwk = JSON.parse(env.CLIENT_ASSERTION_KEY); + + const client = new OAuthClient({ + metadata: { + client_id: site + '/oauth-client-metadata.json', + redirect_uris: [site + REDIRECT_PATH], + scope: scopes, + jwks_uri: site + '/oauth/jwks.json' + }, + keyset: [key], + actorResolver, + stores + }); + + clientCache.set(cacheKey, client); + return client; +} + +export type { OAuthSession }; diff --git a/src/lib/atproto/server/repo.remote.ts b/src/lib/atproto/server/repo.remote.ts new file mode 100644 index 0000000..919aac3 --- /dev/null +++ b/src/lib/atproto/server/repo.remote.ts @@ -0,0 +1,130 @@ +import { error } from '@sveltejs/kit'; +import { command, getRequestEvent } from '$app/server'; +import * as v from 'valibot'; +import { collections } from '../settings'; +import { contrail, ensureInit } from '$lib/contrail'; + +// Validate collection format and check against allowed list +const collectionSchema = v.pipe( + v.string(), + v.regex(/^[a-zA-Z][a-zA-Z0-9-]*(\.[a-zA-Z][a-zA-Z0-9-]*){2,}$/), + v.check( + (c) => collections.includes(c as (typeof collections)[number]), + 'Collection not in allowed list' + ) +); + +// AT Protocol rkey: TID, 'self', or other valid record keys +const rkeySchema = v.optional(v.pipe(v.string(), v.regex(/^[a-zA-Z0-9._:~-]{1,512}$/))); + +export const putRecord = command( + v.object({ + collection: collectionSchema, + rkey: rkeySchema, + record: v.record(v.string(), v.unknown()) + }), + async (input) => { + const { locals } = getRequestEvent(); + if (!locals.client || !locals.did) error(401, 'Not authenticated'); + + const response = await locals.client.post('com.atproto.repo.putRecord', { + input: { + collection: input.collection as `${string}.${string}.${string}`, + repo: locals.did, + rkey: input.rkey || 'self', + record: input.record + } + }); + + if (!response.ok) error(500, 'Failed to put record'); + + // Immediately index in contrail + const { platform } = getRequestEvent(); + const db = platform?.env?.DB; + if (db) { + await ensureInit(db); + await contrail.notify(response.data.uri, db).catch(() => {}); + } + + return response.data; + } +); + +export const deleteRecord = command( + v.object({ + collection: collectionSchema, + rkey: rkeySchema + }), + async (input) => { + const { locals } = getRequestEvent(); + if (!locals.client || !locals.did) error(401, 'Not authenticated'); + + const response = await locals.client.post('com.atproto.repo.deleteRecord', { + input: { + collection: input.collection as `${string}.${string}.${string}`, + repo: locals.did, + rkey: input.rkey || 'self' + } + }); + + return { ok: response.ok }; + } +); + +export const uploadBlob = command( + v.object({ + bytes: v.array(v.number()), + mimeType: v.string() + }), + async (input) => { + const { locals } = getRequestEvent(); + if (!locals.client || !locals.did) error(401, 'Not authenticated'); + + const blob = new Blob([new Uint8Array(input.bytes)], { type: input.mimeType }); + + const response = await locals.client.post('com.atproto.repo.uploadBlob', { + params: { repo: locals.did }, + input: blob + }); + + if (!response.ok) error(500, 'Upload failed'); + + return response.data.blob as { + $type: 'blob'; + ref: { $link: string }; + mimeType: string; + size: number; + }; + } +); + +export const createRecord = command( + v.object({ + collection: collectionSchema, + record: v.record(v.string(), v.unknown()) + }), + async (input) => { + const { locals } = getRequestEvent(); + if (!locals.client || !locals.did) error(401, 'Not authenticated'); + + const response = await locals.client.post('com.atproto.repo.createRecord', { + input: { + collection: input.collection as `${string}.${string}.${string}`, + repo: locals.did, + record: input.record + } + }); + + if (!response.ok) error(500, 'Failed to create record'); + + // Immediately index in contrail + const { platform } = getRequestEvent(); + const db = platform?.env?.DB; + if (db) { + await ensureInit(db); + await contrail.notify(response.data.uri, db).catch(() => {}); + } + + return response.data; + } +); diff --git a/src/lib/atproto/server/scopes.ts b/src/lib/atproto/server/scopes.ts new file mode 100644 index 0000000..48f0776 --- /dev/null +++ b/src/lib/atproto/server/scopes.ts @@ -0,0 +1,10 @@ +import { scope } from '@atcute/oauth-node-client'; +import { collections } from '../settings'; + +export const scopes = [ + 'atproto', + scope.repo({ collection: [...collections] }), + scope.blob({ accept: ['*/*'] }), + 'include:app.bsky.authCreatePosts', + 'include:site.standard.authFull' +]; diff --git a/src/lib/atproto/server/session.ts b/src/lib/atproto/server/session.ts new file mode 100644 index 0000000..d13045c --- /dev/null +++ b/src/lib/atproto/server/session.ts @@ -0,0 +1,69 @@ +import type { Cookies } from '@sveltejs/kit'; +import { Client } from '@atcute/client'; +import type { Did } from '@atcute/lexicons'; +import { + type OAuthSession, + TokenInvalidError, + TokenRefreshError, + TokenRevokedError, + AuthMethodUnsatisfiableError +} from '@atcute/oauth-node-client'; +import { createOAuthClient } from './oauth'; +import { getSignedCookie } from './signed-cookie'; +import { scopes } from './scopes'; + +export type SessionLocals = { + session: OAuthSession | null; + client: Client | null; + did: Did | null; +}; + +/** + * Restores an OAuth session from the signed `did` cookie. + * Deletes the cookie only if the session is genuinely unrecoverable. + */ +export async function restoreSession( + cookies: Cookies, + env?: App.Platform['env'], + domain?: string +): Promise { + const did = getSignedCookie(cookies, 'did') as Did | null; + + if (!did) { + return { session: null, client: null, did: null }; + } + + // If permissions changed since login, invalidate the session + const savedScope = getSignedCookie(cookies, 'scope'); + if (savedScope !== null && savedScope !== scopes.join(' ')) { + cookies.delete('did', { path: '/' }); + cookies.delete('scope', { path: '/' }); + return { session: null, client: null, did: null }; + } + + try { + const oauth = createOAuthClient(env, domain); + const session = await oauth.restore(did); + + return { + session, + client: new Client({ handler: session }), + did + }; + } catch (e) { + console.error('Failed to restore session:', e); + + const isSessionGone = + e instanceof TokenInvalidError || + e instanceof TokenRevokedError || + e instanceof TokenRefreshError || + e instanceof AuthMethodUnsatisfiableError; + + if (isSessionGone) { + cookies.delete('did', { path: '/' }); + cookies.delete('scope', { path: '/' }); + } + + return { session: null, client: null, did: null }; + } +} diff --git a/src/lib/atproto/server/signed-cookie.ts b/src/lib/atproto/server/signed-cookie.ts new file mode 100644 index 0000000..6cde19c --- /dev/null +++ b/src/lib/atproto/server/signed-cookie.ts @@ -0,0 +1,69 @@ +import { createHmac, timingSafeEqual } from 'node:crypto'; + +import type { Cookies } from '@sveltejs/kit'; + +import { env } from '$env/dynamic/private'; +import { dev } from '$app/environment'; + +const SEPARATOR = '.'; + +function getSecret(): string { + const secret = env.COOKIE_SECRET; + if (secret) return secret; + if (dev) return 'dev-cookie-secret-not-for-production'; + throw new Error('COOKIE_SECRET is not set'); +} + +function toBase64Url(bytes: Uint8Array): string { + let binary = ''; + for (const byte of bytes) binary += String.fromCharCode(byte); + return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); +} + +function fromBase64Url(str: string): Uint8Array { + const padded = str + '='.repeat((4 - (str.length % 4)) % 4); + const base64 = padded.replace(/-/g, '+').replace(/_/g, '/'); + const binary = atob(base64); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); + return bytes; +} + +function hmacSha256(data: string): Uint8Array { + return createHmac('sha256', getSecret()).update(data).digest(); +} + +export function getSignedCookie(cookies: Cookies, name: string): string | null { + const signed = cookies.get(name); + if (!signed) return null; + + const idx = signed.lastIndexOf(SEPARATOR); + if (idx === -1) return null; + + const value = signed.slice(0, idx); + const sig = signed.slice(idx + 1); + + let expected: Uint8Array; + let got: Uint8Array; + try { + expected = hmacSha256(value); + got = fromBase64Url(sig); + } catch { + return null; + } + + if (got.length !== expected.length || !timingSafeEqual(got, expected)) return null; + + return value; +} + +export function setSignedCookie( + cookies: Cookies, + name: string, + value: string, + options: Parameters[2] +): void { + const sig = toBase64Url(hmacSha256(value)); + const signed = `${value}${SEPARATOR}${sig}`; + cookies.set(name, signed, options); +} diff --git a/src/lib/atproto/settings.ts b/src/lib/atproto/settings.ts index e812e6b..6166069 100644 --- a/src/lib/atproto/settings.ts +++ b/src/lib/atproto/settings.ts @@ -3,58 +3,29 @@ import { env } from '$env/dynamic/public'; export const SITE = env.PUBLIC_DOMAIN; -type Permissions = { - collections: readonly string[]; - rpc: Record; - blobs: readonly string[]; -}; - -export const permissions = { - // collections you can create/delete/update - - // example: only allow create and delete - // collections: ['xyz.statusphere.status?action=create&action=update'], - collections: [ - 'app.blento.card', - 'app.blento.page', - 'app.blento.settings', - 'app.blento.comment', - 'app.blento.guestbook.entry', - 'site.standard.publication', - 'site.standard.document', - 'xyz.statusphere.status', - 'community.lexicon.calendar.rsvp', - 'community.lexicon.calendar.event', - 'app.nearhorizon.actor.pronouns' - ], - - // what types of authenticated proxied requests you can make to services - - // example: allow authenticated proxying to bsky appview to get a users liked posts - //rpc: {'did:web:api.bsky.app#bsky_appview': ['app.bsky.feed.getActorLikes']} - rpc: {}, - - // what types of blobs you can upload to a users PDS - - // example: allowing video and html uploads - // blobs: ['video/*', 'text/html'] - // example: allowing all blob types - // blobs: ['*/*'] - blobs: ['*/*'] -} as const satisfies Permissions; - -// Extract base collection name (before any query params) -type ExtractCollectionBase = T extends `${infer Base}?${string}` ? Base : T; - -export type AllowedCollection = ExtractCollectionBase<(typeof permissions.collections)[number]>; +export const collections = [ + 'app.blento.card', + 'app.blento.page', + 'app.blento.settings', + 'app.blento.comment', + 'app.blento.guestbook.entry', + 'site.standard.publication', + 'site.standard.document', + 'xyz.statusphere.status', + 'community.lexicon.calendar.rsvp', + 'community.lexicon.calendar.event', + 'app.nearhorizon.actor.pronouns', + 'app.bsky.feed.post' +] as const; + +export type AllowedCollection = (typeof collections)[number]; // which PDS to use for signup -// ATTENTION: pds.rip is only for development, all accounts get deleted automatically after a week const devPDS = 'https://pds.rip/'; const prodPDS = 'https://selfhosted.social/'; export const signUpPDS = dev ? devPDS : prodPDS; -// where to redirect after oauth login/signup, e.g. /oauth/callback +// where to redirect after oauth login/signup export const REDIRECT_PATH = '/oauth/callback'; export const DOH_RESOLVER = 'https://mozilla.cloudflare-dns.com/dns-query'; diff --git a/src/lib/cache.ts b/src/lib/cache.ts index 45a6f18..f470b7f 100644 --- a/src/lib/cache.ts +++ b/src/lib/cache.ts @@ -1,12 +1,9 @@ -import type { ActorIdentifier, Did } from '@atcute/lexicons'; -import { isDid } from '@atcute/lexicons/syntax'; +import type { Did } from '@atcute/lexicons'; import type { KVNamespace } from '@cloudflare/workers-types'; import { getBlentoOrBskyProfile } from '$lib/atproto/methods'; /** TTL in seconds for each cache namespace */ const NAMESPACE_TTL = { - blento: 60 * 60 * 24, // 24 hours - identity: 60 * 60 * 24 * 7, // 7 days github: 60 * 60 * 12, // 12 hours 'gh-contrib': 60 * 60 * 12, // 12 hours lastfm: 60 * 60, // 1 hour (default, overridable per-put) @@ -84,35 +81,6 @@ export class CacheService { await this.kv.put(`${namespace}:${key}`, value, ttl > 0 ? { expirationTtl: ttl } : undefined); } - // === blento data (keyed by DID, with handle↔did resolution) === - async getBlento(identifier: ActorIdentifier): Promise { - const did = await this.resolveDid(identifier); - if (!did) return null; - return this.get('blento', did); - } - - async putBlento(did: string, handle: string, data: string): Promise { - await Promise.all([ - this.put('blento', did, data), - this.put('identity', `h:${handle}`, did), - this.put('identity', `d:${did}`, handle) - ]); - } - - async listBlentos(): Promise { - return this.list('blento'); - } - - // === Identity resolution === - async resolveDid(identifier: ActorIdentifier): Promise { - if (isDid(identifier)) return identifier; - return this.get('identity', `h:${identifier}`); - } - - async resolveHandle(did: Did): Promise { - return this.get('identity', `d:${did}`); - } - // === Profile cache (did → profile data) === async getProfile(did: Did): Promise { const cached = await this.getJSON('profile', did); diff --git a/src/lib/cards/media/SecretImageCard/EditingSecretImageCard.svelte b/src/lib/cards/media/SecretImageCard/EditingSecretImageCard.svelte index 8c8fe89..42d2cb6 100644 --- a/src/lib/cards/media/SecretImageCard/EditingSecretImageCard.svelte +++ b/src/lib/cards/media/SecretImageCard/EditingSecretImageCard.svelte @@ -102,5 +102,4 @@ {/if} - diff --git a/src/lib/cards/media/StatusphereCard/EditStatusphereCard.svelte b/src/lib/cards/media/StatusphereCard/EditStatusphereCard.svelte index 22d8c6a..e376b57 100644 --- a/src/lib/cards/media/StatusphereCard/EditStatusphereCard.svelte +++ b/src/lib/cards/media/StatusphereCard/EditStatusphereCard.svelte @@ -29,9 +29,7 @@ let mode = $derived(item.cardData?.mode ?? 'emoji'); // Emoji mode: use cardData. Statusphere mode: use latest record or preview. let emoji = $derived( - mode === 'statusphere' - ? (item.cardData?.emoji ?? record?.value?.status) - : item.cardData?.emoji + mode === 'statusphere' ? (item.cardData?.emoji ?? record?.value?.status) : item.cardData?.emoji ); let showPopover = $state(false); diff --git a/src/lib/cards/media/StatusphereCard/StatusphereCard.svelte b/src/lib/cards/media/StatusphereCard/StatusphereCard.svelte index d251c4d..4dcd3d8 100644 --- a/src/lib/cards/media/StatusphereCard/StatusphereCard.svelte +++ b/src/lib/cards/media/StatusphereCard/StatusphereCard.svelte @@ -11,9 +11,7 @@ let mode = $derived(item.cardData?.mode ?? 'emoji'); // Emoji mode: use cardData. Statusphere mode: use latest record from PDS. - let emoji = $derived( - mode === 'statusphere' ? record?.value?.status : item.cardData?.emoji - ); + let emoji = $derived(mode === 'statusphere' ? record?.value?.status : item.cardData?.emoji); let animated = $derived(emojiToNotoAnimatedWebp(emoji)); diff --git a/src/lib/cards/social/GuestbookCard/CreateGuestbookCardModal.svelte b/src/lib/cards/social/GuestbookCard/CreateGuestbookCardModal.svelte index 25b343a..09cc40e 100644 --- a/src/lib/cards/social/GuestbookCard/CreateGuestbookCardModal.svelte +++ b/src/lib/cards/social/GuestbookCard/CreateGuestbookCardModal.svelte @@ -59,13 +59,9 @@ const facets = buildFacets(postText, profileUrl); const response = await createPost({ text: postText, facets }); - if (!response.ok) { - throw new Error('Failed to create post'); - } - - item.cardData.uri = response.data.uri; + item.cardData.uri = response.uri; - const rkey = response.data.uri.split('/').pop(); + const rkey = response.uri.split('/').pop(); item.cardData.href = `https://bsky.app/profile/${user.profile?.handle}/post/${rkey}`; oncreate(); diff --git a/src/lib/cards/social/KichRecipeCard/KichRecipeCard.svelte b/src/lib/cards/social/KichRecipeCard/KichRecipeCard.svelte index d9bb75a..84c1ffc 100644 --- a/src/lib/cards/social/KichRecipeCard/KichRecipeCard.svelte +++ b/src/lib/cards/social/KichRecipeCard/KichRecipeCard.svelte @@ -121,7 +121,7 @@ {title}
diff --git a/src/lib/cards/social/KichRecipeCollectionCard/KichRecipeCollectionCard.svelte b/src/lib/cards/social/KichRecipeCollectionCard/KichRecipeCollectionCard.svelte index 3942e39..ba48a39 100644 --- a/src/lib/cards/social/KichRecipeCollectionCard/KichRecipeCollectionCard.svelte +++ b/src/lib/cards/social/KichRecipeCollectionCard/KichRecipeCollectionCard.svelte @@ -84,10 +84,10 @@ class="relative block min-h-0 flex-1" > {#if imageUrl} - {title} + {title} {:else}
{/if} diff --git a/src/lib/cards/special/UpdatedBlentos/index.ts b/src/lib/cards/special/UpdatedBlentos/index.ts index ccd611e..6994807 100644 --- a/src/lib/cards/special/UpdatedBlentos/index.ts +++ b/src/lib/cards/special/UpdatedBlentos/index.ts @@ -1,64 +1,111 @@ import type { CardDefinition } from '../../types'; import UpdatedBlentosCard from './UpdatedBlentosCard.svelte'; +import { getCDNImageBlobUrl } from '$lib/atproto/methods'; +import { getServerClient } from '$lib/contrail'; import type { Did } from '@atcute/lexicons'; -import { getBlentoOrBskyProfile } from '$lib/atproto/methods'; -type ProfileWithBlentoFlag = Awaited>; +type ProfileWithBlentoFlag = { + did: Did; + handle: `${string}.${string}`; + displayName?: string; + avatar?: string; + hasBlento: boolean; + url?: string; +}; + +function extractProfiles( + profiles: Array<{ + did: string; + handle?: string; + collection?: string; + rkey?: string; + record?: unknown; + }> +): Map { + const map = new Map(); + + for (const p of profiles) { + const existing = map.get(p.did) ?? { + did: p.did as Did, + handle: (p.handle ?? p.did) as `${string}.${string}`, + hasBlento: false + }; + + if (p.handle && p.handle !== 'handle.invalid') { + existing.handle = p.handle as `${string}.${string}`; + } + + const record = p.record as Record | undefined; + + if (p.collection === 'app.bsky.actor.profile' && record) { + existing.displayName ??= record.displayName as string | undefined; + if (!existing.avatar && record.avatar) { + const cdnUrl = getCDNImageBlobUrl({ + did: p.did, + blob: record.avatar as { $type: 'blob'; ref: { $link: string } } + }); + if (cdnUrl) existing.avatar = cdnUrl; + } + } + + if (p.collection === 'site.standard.publication' && record) { + existing.hasBlento = true; + existing.displayName = (record.name as string) ?? existing.displayName; + existing.url = record.url as string | undefined; + if (record.icon) { + const cdnUrl = getCDNImageBlobUrl({ + did: p.did, + blob: record.icon as { $type: 'blob'; ref: { $link: string } } + }); + if (cdnUrl) existing.avatar = cdnUrl; + } + } + + map.set(p.did, existing); + } + + return map; +} export const UpdatedBlentosCardDefitition = { type: 'updatedBlentos', contentComponent: UpdatedBlentosCard, keywords: ['feed', 'updates', 'recent', 'activity'], - loadData: async (items, { cache }) => { + loadDataServer: async (items, { platform }) => { try { - const response = await fetch( - 'https://ufos-api.microcosm.blue/records?collection=app.blento.card' - ); - const recentRecords = await response.json(); - const existingUsers = await cache?.get('meta', 'updatedBlentos'); - const existingUsersArray: ProfileWithBlentoFlag[] = existingUsers - ? JSON.parse(existingUsers) - : []; + const db = platform?.env?.DB; + if (!db) return []; - const uniqueDids = new Set(recentRecords.map((v: { did: string }) => v.did as Did)); + const client = getServerClient(db); + const res = await client.get('app.blento.card.listRecords', { + params: { limit: 100, profiles: true, sort: 'time_us', order: 'desc' } + }); - const profiles: Promise[] = []; + if (!res.ok) return []; - for (const did of Array.from(uniqueDids)) { - profiles.push(getBlentoOrBskyProfile({ did })); - } + // Build profile map from contrail (includes bsky profile + blento publication) + const profileMap = res.data.profiles ? extractProfiles(res.data.profiles) : new Map(); - for (let i = existingUsersArray.length - 1; i >= 0; i--) { - // if handle is handle.invalid, remove from existing users and add to profiles to refresh - if ( - (existingUsersArray[i].handle === 'handle.invalid' || - (!existingUsersArray[i].avatar && !existingUsersArray[i].hasBlento)) && - !uniqueDids.has(existingUsersArray[i].did) - ) { - const removed = existingUsersArray.splice(i, 1)[0]; - profiles.push(getBlentoOrBskyProfile({ did: removed.did })); - // if in unique dids, remove from older existing users and keep the newer one - // so updated profiles go first - } else if (uniqueDids.has(existingUsersArray[i].did)) { - existingUsersArray.splice(i, 1); + // Extract unique DIDs in order of recency + const seen = new Set(); + const uniqueDids: string[] = []; + for (const r of res.data.records) { + if (!seen.has(r.did) && !r.did.endsWith('.pds.rip')) { + seen.add(r.did); + uniqueDids.push(r.did); } } - let result = [...(await Promise.all(profiles)), ...existingUsersArray]; - - result = result.filter( - (v) => v && v.handle !== 'handle.invalid' && !v.handle.endsWith('.pds.rip') - ); - - await cache?.put('meta', 'updatedBlentos', JSON.stringify(result)); - - return JSON.parse(JSON.stringify(result.slice(0, 20))); + return uniqueDids + .slice(0, 20) + .map((did) => profileMap.get(did)) + .filter( + (v): v is ProfileWithBlentoFlag => + !!v && v.handle !== 'handle.invalid' && !v.handle.endsWith('.pds.rip') + ); } catch (error) { console.error('error fetching updated blentos', error); return []; } } - // name: 'Updated Blentos', - // groups: ['Social'], - // icon: `` } as CardDefinition & { type: 'updatedBlentos' }; diff --git a/src/lib/cards/types.ts b/src/lib/cards/types.ts index 75a94b4..e5e9c83 100644 --- a/src/lib/cards/types.ts +++ b/src/lib/cards/types.ts @@ -48,12 +48,14 @@ export type CardDefinition = { did, handle, cache, - env + env, + platform }: { did: Did; handle: string; cache?: CacheService; env?: Record; + platform?: App.Platform; } ) => Promise; diff --git a/src/lib/contrail/client.ts b/src/lib/contrail/client.ts new file mode 100644 index 0000000..7b44b22 --- /dev/null +++ b/src/lib/contrail/client.ts @@ -0,0 +1,8 @@ +import { Client, simpleFetchHandler } from '@atcute/client'; + +/** + * Client-side: fully typed @atcute/client that queries the app's own /xrpc/ endpoints. + */ +export function getClient() { + return new Client({ handler: simpleFetchHandler({ service: '' }) }); +} diff --git a/src/lib/contrail/config.ts b/src/lib/contrail/config.ts new file mode 100644 index 0000000..96bb3be --- /dev/null +++ b/src/lib/contrail/config.ts @@ -0,0 +1,20 @@ +import type { ContrailConfig } from '@atmo-dev/contrail'; + +export const config: ContrailConfig = { + namespace: 'app.blento', + collections: { + 'app.blento.card': { + queryable: { + page: {}, + cardType: {} + } + }, + 'app.blento.page': { + queryable: {} + } + }, + profiles: [ + 'app.bsky.actor.profile', + { collection: 'site.standard.publication', rkey: 'blento.self' } + ] +}; diff --git a/src/lib/contrail/index.ts b/src/lib/contrail/index.ts new file mode 100644 index 0000000..bb7ba5b --- /dev/null +++ b/src/lib/contrail/index.ts @@ -0,0 +1,32 @@ +import type { D1Database } from '@cloudflare/workers-types'; +import { Contrail } from '@atmo-dev/contrail'; +import { createHandler } from '@atmo-dev/contrail/server'; +import { Client } from '@atcute/client'; +import { config } from './config'; + +export const contrail = new Contrail(config); + +let initialized = false; + +export async function ensureInit(db: D1Database) { + if (!initialized) { + await contrail.init(db); + initialized = true; + } +} + +const handle = createHandler(contrail); + +/** + * Server-side: fully typed @atcute/client that routes through contrail in-process. + * No HTTP roundtrip — calls createHandler directly. + */ +export function getServerClient(db: D1Database) { + return new Client({ + handler: async (pathname, init) => { + await ensureInit(db); + const url = new URL(pathname, 'http://localhost'); + return handle(new Request(url, init), db) as Promise; + } + }); +} diff --git a/src/lib/helper.ts b/src/lib/helper.ts index d891902..9e88ba3 100644 --- a/src/lib/helper.ts +++ b/src/lib/helper.ts @@ -49,20 +49,9 @@ export function cardsEqual(a: Item, b: Item) { ); } -export async function refreshData(data: { updatedAt?: number; handle: string }) { - const TEN_MINUTES = 10 * 60 * 1000; - const now = Date.now(); - - if (now - (data.updatedAt || 0) > TEN_MINUTES) { - try { - await fetch('/' + data.handle + '/api/refresh'); - console.log('successfully refreshed data', data.handle); - } catch (error) { - console.error('error refreshing data', error); - } - } else { - console.log('data still fresh, skipping refreshing', data.handle); - } +/** @deprecated No longer needed — Contrail keeps data fresh via notify() */ +export async function refreshData(_data: { updatedAt?: number; handle: string }) { + // no-op: Contrail indexes are updated immediately on writes } export function getName(data: WebsiteData): string { diff --git a/src/lib/website/CustomDomainModal.svelte b/src/lib/website/CustomDomainModal.svelte index 095a93e..fb88426 100644 --- a/src/lib/website/CustomDomainModal.svelte +++ b/src/lib/website/CustomDomainModal.svelte @@ -118,11 +118,6 @@ return; } - // Refresh cached profile - if (user.profile) { - fetch(`/${getHandleOrDid(user.profile)}/api/refresh`).catch(() => {}); - } - launchConfetti(); step = 'success'; } catch (err: unknown) { diff --git a/src/lib/website/EditableWebsite.svelte b/src/lib/website/EditableWebsite.svelte index 165917a..cf44e2e 100644 --- a/src/lib/website/EditableWebsite.svelte +++ b/src/lib/website/EditableWebsite.svelte @@ -54,7 +54,7 @@ } = $props(); // Check if floating login button will be visible (to hide MadeWithBlento) - const showLoginOnEditPage = $derived(!user.isInitializing && !user.isLoggedIn); + const showLoginOnEditPage = $derived(!user.isLoggedIn); // svelte-ignore state_referenced_locally let items: Item[] = $state(data.cards); @@ -239,8 +239,6 @@ launchConfetti(); - // Refresh cached data - await fetch('/' + data.handle + '/api/refresh'); } catch (error) { console.error(error); showSaveModal = false; diff --git a/src/lib/website/FloatingEditButton.svelte b/src/lib/website/FloatingEditButton.svelte index cf8e9a5..1e15c3a 100644 --- a/src/lib/website/FloatingEditButton.svelte +++ b/src/lib/website/FloatingEditButton.svelte @@ -14,9 +14,9 @@ const isBlento = $derived(!env.PUBLIC_IS_SELFHOSTED && data.handle === 'blento.app'); const isEditPage = $derived(page.url.pathname.endsWith('/edit')); const showLoginOnBlento = $derived( - isBlento && !user.isInitializing && !user.isLoggedIn && user.profile?.handle !== data.handle + isBlento && !user.isLoggedIn && user.profile?.handle !== data.handle ); - const showLoginOnEditPage = $derived(isEditPage && !user.isInitializing && !user.isLoggedIn); + const showLoginOnEditPage = $derived(isEditPage && !user.isLoggedIn); const showEditBlentoButton = $derived( isBlento && user.isLoggedIn && user.profile?.handle !== data.handle ); diff --git a/src/lib/website/Website.svelte b/src/lib/website/Website.svelte index 63b7f83..5619c01 100644 --- a/src/lib/website/Website.svelte +++ b/src/lib/website/Website.svelte @@ -31,11 +31,11 @@ const isOwnPage = $derived(user.isLoggedIn && user.profile?.did === data.did); const isBlento = $derived(!env.PUBLIC_IS_SELFHOSTED && data.handle === 'blento.app'); const isEditPage = $derived(page.url.pathname.endsWith('/edit')); - const showLoginOnEditPage = $derived(isEditPage && !user.isInitializing && !user.isLoggedIn); + const showLoginOnEditPage = $derived(isEditPage && !user.isLoggedIn); const showFloatingButton = $derived( (isOwnPage && !isEditPage) || showLoginOnEditPage || - (isBlento && !user.isInitializing && !user.isLoggedIn) || + (isBlento && !user.isLoggedIn) || (isBlento && user.isLoggedIn && user.profile?.handle !== data.handle) ); diff --git a/src/lib/website/load.ts b/src/lib/website/load.ts index c65a075..a686269 100644 --- a/src/lib/website/load.ts +++ b/src/lib/website/load.ts @@ -1,4 +1,5 @@ import { getDetailedProfile, listRecords, resolveHandle, parseUri, getRecord } from '$lib/atproto'; +import { getCDNImageBlobUrl } from '$lib/atproto/methods'; import { CardDefinitionsByType } from '$lib/cards'; import type { CacheService } from '$lib/cache'; import { createEmptyCard } from '$lib/helper'; @@ -6,55 +7,134 @@ import type { Item, PronounsRecord, WebsiteData } from '$lib/types'; import { error } from '@sveltejs/kit'; import type { ActorIdentifier, Did } from '@atcute/lexicons'; +import type { D1Database } from '@cloudflare/workers-types'; import { isDid, isHandle } from '@atcute/lexicons/syntax'; import { fixAllCollisions, compactItems } from '$lib/layout'; - -const CURRENT_CACHE_VERSION = 1; +import { getServerClient } from '$lib/contrail'; +import type { AppBskyActorDefs } from '@atcute/bluesky'; function formatPronouns( record: PronounsRecord | undefined, profile: WebsiteData['profile'] | undefined ): string | undefined { - // nearhorizon.actor.pronouns - https://github.com/skydeval/atproto-pronouns if (record?.value?.sets?.length) { const sets = record.value.sets; const displayMode = record.value.displayMode ?? 'all'; const setsToShow = displayMode === 'firstOnly' ? sets.slice(0, 1) : sets; return setsToShow.map((s) => s.forms.join('/')).join(' · '); } - // fallback to bsky pronouns const pronouns = (profile as Record)?.pronouns; if (pronouns && typeof pronouns === 'string') return pronouns; return undefined; } -export async function getCache(identifier: ActorIdentifier, page: string, cache?: CacheService) { - try { - const cachedResult = await cache?.getBlento(identifier); - - if (!cachedResult) return; - const result = JSON.parse(cachedResult); +function resolveDid(handle: ActorIdentifier): Promise { + if (isDid(handle)) return Promise.resolve(handle); + if (isHandle(handle)) return resolveHandle({ handle }); + return Promise.resolve(undefined); +} - if (!result.version || result.version !== CURRENT_CACHE_VERSION) { - console.log('skipping cache because of version mismatch'); - return; +type ContrailProfile = { + did: string; + handle?: string; + collection?: string; + rkey?: string; + record?: unknown; +}; + +/** + * Extract a bsky-style profile and publication from contrail profile entries. + */ +function extractProfileData( + did: string, + profiles: ContrailProfile[] +): { + profile: AppBskyActorDefs.ProfileViewDetailed; + publication: WebsiteData['publication'] | undefined; +} { + let bskyRecord: Record | undefined; + let pubRecord: Record | undefined; + let handle = did; + + for (const p of profiles) { + if (p.did !== did) continue; + if (p.handle && p.handle !== 'handle.invalid') handle = p.handle; + + const record = p.record as Record | undefined; + if (p.collection === 'app.bsky.actor.profile' && record) { + bskyRecord = record; } + if (p.collection === 'site.standard.publication' && record) { + pubRecord = record; + } + } - result.page = 'blento.' + page; + const avatar = bskyRecord?.avatar + ? getCDNImageBlobUrl({ + did, + blob: bskyRecord.avatar as { $type: 'blob'; ref: { $link: string } } + }) + : undefined; + + const profile = { + did: did as Did, + handle: handle as `${string}.${string}`, + displayName: (bskyRecord?.displayName as string) ?? handle, + description: bskyRecord?.description as string | undefined, + avatar + } as AppBskyActorDefs.ProfileViewDetailed; + + const publication = pubRecord + ? (pubRecord as WebsiteData['publication']) + : undefined; + + return { profile, publication }; +} - result.publication = (result.publications as Awaited>).find( - (v) => parseUri(v.uri)?.rkey === result.page - )?.value; - result.publication ??= { - name: result.profile?.displayName || result.profile?.handle, - description: result.profile?.description +/** + * Load all data for a user from Contrail in a single call (cards + profiles). + */ +async function loadFromContrail( + actor: ActorIdentifier, + db: D1Database +): Promise<{ + cards: Item[]; + pages: Awaited>; + profiles: ContrailProfile[]; +} | null> { + try { + const client = getServerClient(db); + const [cardRes, pageRes] = await Promise.all([ + client.get('app.blento.card.listRecords', { + params: { actor, limit: 200, profiles: true } + }), + client.get('app.blento.page.listRecords', { + params: { actor, limit: 200 } + }) + ]); + + if (!cardRes.ok) return null; + + const cards = cardRes.data.records.map((r) => ({ ...(r.record as object) }) as Item); + + const pages = pageRes.ok + ? pageRes.data.records + .filter((r) => r.record) + .map((r) => ({ + uri: r.uri, + cid: r.cid ?? '', + value: r.record as Record + })) + : []; + + return { + cards, + pages, + profiles: (cardRes.data.profiles ?? []) as ContrailProfile[] }; - - delete result['publications']; - - return checkData(result); - } catch (error) { - console.log('getting cached result failed', error); + } catch (e) { + console.error('Contrail query failed', e); + return null; } } @@ -63,92 +143,100 @@ export async function loadData( cache: CacheService | undefined, forceUpdate: boolean = false, page: string = 'self', - env?: Record + env?: Record, + platform?: App.Platform ): Promise { if (!handle) throw error(404); if (handle === 'favicon.ico') throw error(404); - if (!forceUpdate) { - const cachedResult = await getCache(handle, page, cache); + const did = await resolveDid(handle); + if (!did) throw error(404); - if (cachedResult) return cachedResult; - } + const db = platform?.env?.DB; + const contrailData = db ? await loadFromContrail(handle, db) : null; - let did: Did | undefined = undefined; - if (isHandle(handle)) { - did = await resolveHandle({ handle }); - } else if (isDid(handle)) { - did = handle; - } else { - throw error(404); - } + let cards: Item[]; + let pageRecords: Awaited>; + let profile: WebsiteData['profile']; + let publication: WebsiteData['publication'] | undefined; + let pronounsRecord: PronounsRecord | undefined; - const [cards, mainPublication, pages, profile, pronounsRecord] = await Promise.all([ - listRecords({ did, collection: 'app.blento.card', limit: 0 }).catch((e) => { - console.error('error getting records for collection app.blento.card', e); - return [] as Awaited>; - }), - getRecord({ - did, - collection: 'site.standard.publication', - rkey: 'blento.self' - }).catch(() => { - console.error('error getting record for collection site.standard.publication'); - return undefined; - }), - listRecords({ did, collection: 'app.blento.page' }).catch(() => { - console.error('error getting records for collection app.blento.page'); - return [] as Awaited>; - }), - getDetailedProfile({ did }), - getRecord({ + if (contrailData) { + cards = contrailData.cards; + pageRecords = contrailData.pages; + + const extracted = extractProfileData(did, contrailData.profiles); + profile = extracted.profile; + publication = extracted.publication; + + // Pronouns still from PDS (not in contrail) + pronounsRecord = await getRecord({ did, collection: 'app.nearhorizon.actor.pronouns', rkey: 'self' - }).catch(() => undefined) - ]); + }).catch(() => undefined) as PronounsRecord | undefined; + } else { + // Fallback: no D1 available (e.g. vite dev) — use PDS directly + const [cardRecords, pageRecs, mainPub, prof, pronouns] = await Promise.all([ + listRecords({ did, collection: 'app.blento.card', limit: 0 }).catch((e) => { + console.error('error getting records for collection app.blento.card', e); + return [] as Awaited>; + }), + listRecords({ did, collection: 'app.blento.page' }).catch(() => { + return [] as Awaited>; + }), + getRecord({ + did, + collection: 'site.standard.publication', + rkey: 'blento.self' + }).catch(() => undefined), + getDetailedProfile({ did }), + getRecord({ + did, + collection: 'app.nearhorizon.actor.pronouns', + rkey: 'self' + }).catch(() => undefined) + ]); + + cards = cardRecords.map((v) => ({ ...v.value }) as Item); + pageRecords = pageRecs; + profile = prof; + publication = mainPub?.value as WebsiteData['publication'] | undefined; + pronounsRecord = pronouns as PronounsRecord | undefined; + } + + // If no publication found from contrail profiles, check page records + if (!publication) { + const pubFromPages = pageRecords.find( + (v) => parseUri(v.uri)?.rkey === 'blento.' + page + ); + publication = pubFromPages?.value as WebsiteData['publication'] | undefined; + } + + publication ??= { + name: profile?.displayName || profile?.handle, + description: profile?.description + } as WebsiteData['publication']; const additionalData = await loadAdditionalData( - cards.map((v) => ({ ...v.value })) as Item[], - { did, handle, cache }, + cards, + { did, handle, cache, platform }, env ); - const result = { + return checkData({ page: 'blento.' + page, handle, did, - cards: (cards.map((v) => { - return { ...v.value }; - }) ?? []) as Item[], - publications: [mainPublication, ...pages].filter((v) => v), + cards, + publication, additionalData, profile, pronouns: formatPronouns(pronounsRecord, profile), - pronounsRecord: pronounsRecord as PronounsRecord | undefined, + pronounsRecord, updatedAt: Date.now(), - version: CURRENT_CACHE_VERSION - }; - - // Only cache results that have cards to avoid caching PDS errors - if (result.cards.length > 0) { - const stringifiedResult = JSON.stringify(result); - await cache?.putBlento(did, handle as string, stringifiedResult); - } - - const parsedResult = structuredClone(result) as any; - - parsedResult.publication = ( - parsedResult.publications as Awaited> - ).find((v) => parseUri(v.uri)?.rkey === parsedResult.page)?.value; - parsedResult.publication ??= { - name: profile?.displayName || profile?.handle, - description: profile?.description - }; - - delete parsedResult['publications']; - - return checkData(parsedResult); + version: 1 + }); } export async function loadCardData( @@ -160,14 +248,8 @@ export async function loadCardData( if (!handle) throw error(404); if (handle === 'favicon.ico') throw error(404); - let did: Did | undefined = undefined; - if (isHandle(handle)) { - did = await resolveHandle({ handle }); - } else if (isDid(handle)) { - did = handle; - } else { - throw error(404); - } + const did = await resolveDid(handle); + if (!did) throw error(404); const [cardRecord, profile, pronounsRecord] = await Promise.all([ getRecord({ @@ -205,7 +287,7 @@ export async function loadCardData( env ); - const result = { + return { page, handle: resolvedHandle, did, @@ -221,10 +303,8 @@ export async function loadCardData( pronouns: formatPronouns(pronounsRecord, profile), pronounsRecord: pronounsRecord as PronounsRecord | undefined, updatedAt: Date.now(), - version: CURRENT_CACHE_VERSION + version: 1 }; - - return result; } export async function loadCardTypeData( @@ -242,14 +322,8 @@ export async function loadCardTypeData( throw error(404, 'Card type not found'); } - let did: Did | undefined = undefined; - if (isHandle(handle)) { - did = await resolveHandle({ handle }); - } else if (isDid(handle)) { - did = handle; - } else { - throw error(404); - } + const did = await resolveDid(handle); + if (!did) throw error(404); const [publication, profile, pronounsRecord] = await Promise.all([ getRecord({ @@ -283,7 +357,7 @@ export async function loadCardTypeData( env ); - const result = { + return checkData({ page: 'blento.self', handle: resolvedHandle, did, @@ -299,10 +373,8 @@ export async function loadCardTypeData( pronouns: formatPronouns(pronounsRecord, profile), pronounsRecord: pronounsRecord as PronounsRecord | undefined, updatedAt: Date.now(), - version: CURRENT_CACHE_VERSION - }; - - return checkData(result); + version: 1 + }); } function migrateCard(card: Item): Item { @@ -331,7 +403,12 @@ function migrateCard(card: Item): Item { async function loadAdditionalData( cards: Item[], - { did, handle, cache }: { did: Did; handle: string; cache?: CacheService }, + { + did, + handle, + cache, + platform + }: { did: Did; handle: string; cache?: CacheService; platform?: App.Platform }, env?: Record ) { const cardTypes = new Set(cards.map((v) => v.cardType ?? '') as string[]); @@ -348,7 +425,8 @@ async function loadAdditionalData( did, handle, cache, - env + env, + platform }); } else if (cardDef?.loadData) { additionDataPromises[cardType] = cardDef.loadData(items, { did, handle, cache }); diff --git a/src/routes/(auth)/oauth-client-metadata.json/+server.ts b/src/routes/(auth)/oauth-client-metadata.json/+server.ts index 4ac1cc7..ee5590b 100644 --- a/src/routes/(auth)/oauth-client-metadata.json/+server.ts +++ b/src/routes/(auth)/oauth-client-metadata.json/+server.ts @@ -1,17 +1,9 @@ -import { metadata } from '$lib/atproto'; import { json } from '@sveltejs/kit'; +import { createOAuthClient } from '$lib/atproto/server/oauth'; -export async function GET({ request }) { +export async function GET({ request, platform }) { const customDomain = request.headers.get('X-Custom-Domain')?.toLowerCase(); + const oauth = createOAuthClient(platform?.env, customDomain || undefined); - if (customDomain) { - const changedMetadata = metadata; - changedMetadata.redirect_uris = changedMetadata.redirect_uris.map((s) => - s.replace('blento.app', customDomain) - ); - changedMetadata.client_id = changedMetadata.client_id.replace('blento.app', customDomain); - return json(changedMetadata); - } - - return json(metadata); + return json(oauth.metadata); } diff --git a/src/routes/(auth)/oauth/callback/+page.svelte b/src/routes/(auth)/oauth/callback/+page.svelte deleted file mode 100644 index 83e7aca..0000000 --- a/src/routes/(auth)/oauth/callback/+page.svelte +++ /dev/null @@ -1,54 +0,0 @@ - - -{#if !showError} -
Loading...
-{:else} -
- There was an error signing you in, please go back to the - homepage - and try again. - -
-{/if} diff --git a/src/routes/(auth)/oauth/callback/+server.ts b/src/routes/(auth)/oauth/callback/+server.ts new file mode 100644 index 0000000..3e57966 --- /dev/null +++ b/src/routes/(auth)/oauth/callback/+server.ts @@ -0,0 +1,40 @@ +import { redirect } from '@sveltejs/kit'; +import { createOAuthClient } from '$lib/atproto/server/oauth'; +import { setSignedCookie } from '$lib/atproto/server/signed-cookie'; +import { scopes } from '$lib/atproto/server/scopes'; +import { dev } from '$app/environment'; +import type { RequestHandler } from './$types'; + +export const GET: RequestHandler = async ({ url, platform, cookies, request }) => { + const customDomain = request.headers.get('X-Custom-Domain')?.toLowerCase() || undefined; + const oauth = createOAuthClient(platform?.env, customDomain); + + try { + const { session } = await oauth.callback(url.searchParams); + + const cookieOpts = { + path: '/', + httpOnly: true, + secure: !dev, + sameSite: 'lax' as const, + maxAge: 60 * 60 * 24 * 180 // 180 days + }; + + setSignedCookie(cookies, 'did', session.did, cookieOpts); + setSignedCookie(cookies, 'scope', scopes.join(' '), cookieOpts); + } catch (e) { + console.error('OAuth callback failed:', e); + redirect(303, '/?error=auth_failed'); + } + + const returnTo = cookies.get('oauth_return_to'); + if (returnTo) { + cookies.delete('oauth_return_to', { path: '/' }); + const decoded = decodeURIComponent(returnTo); + if (decoded.startsWith('/') && !decoded.startsWith('//')) { + redirect(303, decoded); + } + } + + redirect(303, '/'); +}; diff --git a/src/routes/(auth)/oauth/jwks.json/+server.ts b/src/routes/(auth)/oauth/jwks.json/+server.ts new file mode 100644 index 0000000..ae4dd84 --- /dev/null +++ b/src/routes/(auth)/oauth/jwks.json/+server.ts @@ -0,0 +1,8 @@ +import { json } from '@sveltejs/kit'; +import { createOAuthClient } from '$lib/atproto/server/oauth'; +import type { RequestHandler } from './$types'; + +export const GET: RequestHandler = async ({ platform }) => { + const oauth = createOAuthClient(platform?.env); + return json(oauth.jwks ?? { keys: [] }); +}; diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts index c7e11d2..68458bc 100644 --- a/src/routes/+layout.server.ts +++ b/src/routes/+layout.server.ts @@ -1,5 +1,8 @@ -export async function load({ request }) { +export async function load({ request, locals, platform }) { const customDomain = request.headers.get('X-Custom-Domain')?.toLowerCase(); - return { customDomain }; + return { + customDomain, + authDid: locals.did + }; } diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index d6e46ba..4e76940 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -3,8 +3,6 @@ import { Tooltip } from 'bits-ui'; import { ThemeToggle, Toaster, toast } from '@foxui/core'; - import { onMount } from 'svelte'; - import { initClient } from '$lib/atproto'; import YoutubeVideoPlayer, { videoPlayer } from '$lib/components/YoutubeVideoPlayer.svelte'; import { page } from '$app/state'; import { goto } from '$app/navigation'; @@ -20,10 +18,6 @@ const errorMessages: Record string> = { handle_not_found: (p) => `Handle ${p.get('handle') ?? ''} not found!` }; - - onMount(() => { - initClient({ customDomain: data.customDomain }); - }); diff --git a/src/routes/[[actor=actor]]/(pages)/+layout.server.ts b/src/routes/[[actor=actor]]/(pages)/+layout.server.ts index 994e277..b26f9d4 100644 --- a/src/routes/[[actor=actor]]/(pages)/+layout.server.ts +++ b/src/routes/[[actor=actor]]/(pages)/+layout.server.ts @@ -15,5 +15,5 @@ export async function load({ params, platform, request }) { throw error(404, 'Page not found'); } - return await loadData(actor, cache, false, params.page, env); + return await loadData(actor, cache, false, params.page, env, platform); } diff --git a/src/routes/[[actor=actor]]/(pages)/p/[[page]]/copy/+page.svelte b/src/routes/[[actor=actor]]/(pages)/p/[[page]]/copy/+page.svelte index e180344..dba05d3 100644 --- a/src/routes/[[actor=actor]]/(pages)/p/[[page]]/copy/+page.svelte +++ b/src/routes/[[actor=actor]]/(pages)/p/[[page]]/copy/+page.svelte @@ -153,9 +153,6 @@ } } - // Refresh the logged-in user's cache - await fetch(`/${userHandle}/api/refresh`); - success = true; // Redirect to the logged-in user's destination page edit diff --git a/src/routes/[[actor=actor]]/.well-known/site.standard.publication/+server.ts b/src/routes/[[actor=actor]]/.well-known/site.standard.publication/+server.ts index 7ddcc54..aed7c2c 100644 --- a/src/routes/[[actor=actor]]/.well-known/site.standard.publication/+server.ts +++ b/src/routes/[[actor=actor]]/.well-known/site.standard.publication/+server.ts @@ -14,7 +14,7 @@ export async function GET({ params, platform, request }) { throw error(404, 'Page not found'); } - const data = await loadData(actor, cache, false, params.page, env); + const data = await loadData(actor, cache, false, params.page, env, platform); if (!data.publication) throw error(300); diff --git a/src/routes/[[actor=actor]]/api/refresh/+server.ts b/src/routes/[[actor=actor]]/api/refresh/+server.ts deleted file mode 100644 index 8f0bbc2..0000000 --- a/src/routes/[[actor=actor]]/api/refresh/+server.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { createCache } from '$lib/cache'; -import { loadData } from '$lib/website/load.js'; -import { env } from '$env/dynamic/private'; -import { error, json } from '@sveltejs/kit'; -import { getActor } from '$lib/actor'; - -export async function GET({ params, platform, request }) { - const cache = createCache(platform); - if (!cache) return json('no cache'); - - const actor = await getActor({ request, paramActor: params.actor, platform, blockBoth: false }); - - if (!actor) { - throw error(404, 'Page not found'); - } - - // Invalidate cached OG image so it gets regenerated - cache.delete('og', actor).catch(() => {}); - - return json(await loadData(actor, cache, true, 'self', env)); -} diff --git a/src/routes/[[actor=actor]]/blog/new/+page.svelte b/src/routes/[[actor=actor]]/blog/new/+page.svelte index 278e8d3..701a9af 100644 --- a/src/routes/[[actor=actor]]/blog/new/+page.svelte +++ b/src/routes/[[actor=actor]]/blog/new/+page.svelte @@ -1,7 +1,7 @@ diff --git a/src/lib/website/load.ts b/src/lib/website/load.ts index 8a415c6..4dc089f 100644 --- a/src/lib/website/load.ts +++ b/src/lib/website/load.ts @@ -96,63 +96,52 @@ function extractProfileData( return { profile, publication, pronounsRecord }; } +async function tryContrail(label: string, fn: () => Promise): Promise { + try { + return await fn(); + } catch (e) { + console.error(`Contrail ${label} failed`, e); + return null; + } +} + /** * Fetch only the profile bundle (bsky profile + publication + pronouns) from contrail. * Used by single-card routes that don't need the full card list. */ -async function loadProfilesFromContrail( - actor: ActorIdentifier, - db: D1Database -): Promise { - try { - const client = getServerClient(db); - const res = await client.get('app.blento.getProfile', { params: { actor } }); +function loadProfilesFromContrail(actor: ActorIdentifier, db: D1Database) { + return tryContrail('getProfile', async () => { + const res = await getServerClient(db).get('app.blento.getProfile', { params: { actor } }); if (!res.ok) return null; return (res.data.profiles ?? []) as ContrailProfile[]; - } catch (e) { - console.error('Contrail getProfile failed', e); - return null; - } + }); } /** * Fetch a single card from contrail by DID + rkey. */ -async function loadCardFromContrail( - did: Did, - rkey: string, - db: D1Database -): Promise { - try { - const client = getServerClient(db); +function loadCardFromContrail(did: Did, rkey: string, db: D1Database) { + return tryContrail('card.getRecord', async () => { const uri = `at://${did}/app.blento.card/${rkey}` as const; - const res = await client.get('app.blento.card.getRecord', { + const res = await getServerClient(db).get('app.blento.card.getRecord', { params: { uri } }); if (!res.ok) return null; return { ...(res.data.record as object) } as Item; - } catch (e) { - console.error('Contrail card.getRecord failed', e); - return null; - } + }); } /** - * Load all data for a user from Contrail in a single call (cards + profiles). + * Load cards for a single page + all pages for the actor from Contrail. + * Filtering cards by page at query time means we only run additional-data loaders + * for card types that actually appear on the current page. */ -async function loadFromContrail( - actor: ActorIdentifier, - db: D1Database -): Promise<{ - cards: Item[]; - pages: Awaited>; - profiles: ContrailProfile[]; -} | null> { - try { +function loadFromContrail(actor: ActorIdentifier, db: D1Database, page: string) { + return tryContrail('cards+pages query', async () => { const client = getServerClient(db); const [cardRes, pageRes] = await Promise.all([ client.get('app.blento.card.listRecords', { - params: { actor, limit: 200, profiles: true } + params: { actor, limit: 200, profiles: true, page } }), client.get('app.blento.page.listRecords', { params: { actor, limit: 200 } @@ -178,16 +167,37 @@ async function loadFromContrail( pages, profiles: (cardRes.data.profiles ?? []) as ContrailProfile[] }; - } catch (e) { - console.error('Contrail query failed', e); - return null; - } + }); +} + +function defaultPublication( + profile: WebsiteData['profile'] | undefined +): WebsiteData['publication'] { + return { + name: profile?.displayName || profile?.handle, + description: profile?.description + } as WebsiteData['publication']; +} + +function getPronounsFromPDS(did: Did) { + return getRecord({ + did, + collection: 'app.nearhorizon.actor.pronouns', + rkey: 'self' + }).catch(() => undefined) as Promise; +} + +function getSelfPublicationFromPDS(did: Did) { + return getRecord({ + did, + collection: 'site.standard.publication', + rkey: 'blento.self' + }).catch(() => undefined); } export async function loadData( handle: ActorIdentifier, cache: CacheService | undefined, - forceUpdate: boolean = false, page: string = 'self', env?: Record, platform?: App.Platform @@ -199,7 +209,8 @@ export async function loadData( if (!did) throw error(404); const db = platform?.env?.DB; - const contrailData = db ? await loadFromContrail(handle, db) : null; + const fullPage = 'blento.' + page; + const contrailData = db ? await loadFromContrail(handle, db, fullPage) : null; let cards: Item[]; let pageRecords: Awaited>; @@ -222,27 +233,19 @@ export async function loadData( console.error('error getting records for collection app.blento.card', e); return [] as Awaited>; }), - listRecords({ did, collection: 'app.blento.page' }).catch(() => { - return [] as Awaited>; - }), - getRecord({ - did, - collection: 'site.standard.publication', - rkey: 'blento.self' - }).catch(() => undefined), + listRecords({ did, collection: 'app.blento.page' }).catch( + () => [] as Awaited> + ), + getSelfPublicationFromPDS(did), getDetailedProfile({ did }), - getRecord({ - did, - collection: 'app.nearhorizon.actor.pronouns', - rkey: 'self' - }).catch(() => undefined) + getPronounsFromPDS(did) ]); cards = cardRecords.map((v) => ({ ...v.value }) as Item); pageRecords = pageRecs; profile = prof; publication = mainPub?.value as WebsiteData['publication'] | undefined; - pronounsRecord = pronouns as PronounsRecord | undefined; + pronounsRecord = pronouns; } // If no publication found from contrail profiles, check page records @@ -251,10 +254,7 @@ export async function loadData( publication = pubFromPages?.value as WebsiteData['publication'] | undefined; } - publication ??= { - name: profile?.displayName || profile?.handle, - description: profile?.description - } as WebsiteData['publication']; + publication ??= defaultPublication(profile); const additionalData = await loadAdditionalData(cards, { did, handle, cache, platform }, env); @@ -315,17 +315,13 @@ export async function loadCardData( const [cardRecord, prof, pronouns] = await Promise.all([ getRecord({ did, collection: 'app.blento.card', rkey }).catch(() => undefined), getDetailedProfile({ did }), - getRecord({ - did, - collection: 'app.nearhorizon.actor.pronouns', - rkey: 'self' - }).catch(() => undefined) + getPronounsFromPDS(did) ]); if (!cardRecord?.value) throw error(404, 'Card not found'); cardValue = cardRecord.value as Item; profile = prof; - pronounsRecord = pronouns as PronounsRecord | undefined; + pronounsRecord = pronouns; } if (!profile) throw error(404); @@ -357,12 +353,7 @@ export async function loadCardData( handle: resolvedHandle, did, cards, - publication: - publication ?? - ({ - name: profile?.displayName || profile?.handle, - description: profile?.description - } as WebsiteData['publication']), + publication: publication ?? defaultPublication(profile), additionalData, profile, pronouns: formatPronouns(pronounsRecord, profile), @@ -409,21 +400,13 @@ export async function loadCardTypeData( if (!profile) { const [pubRecord, prof, pronouns] = await Promise.all([ - getRecord({ - did, - collection: 'site.standard.publication', - rkey: 'blento.self' - }).catch(() => undefined), + getSelfPublicationFromPDS(did), getDetailedProfile({ did }), - getRecord({ - did, - collection: 'app.nearhorizon.actor.pronouns', - rkey: 'self' - }).catch(() => undefined) + getPronounsFromPDS(did) ]); profile = prof; publication = pubRecord?.value as WebsiteData['publication'] | undefined; - pronounsRecord = pronouns as PronounsRecord | undefined; + pronounsRecord = pronouns; } if (!profile) throw error(404); @@ -451,12 +434,7 @@ export async function loadCardTypeData( handle: resolvedHandle, did, cards, - publication: - publication ?? - ({ - name: profile?.displayName || profile?.handle, - description: profile?.description - } as WebsiteData['publication']), + publication: publication ?? defaultPublication(profile), additionalData, profile, pronouns: formatPronouns(pronounsRecord, profile), diff --git a/src/routes/[[actor=actor]]/(pages)/+layout.server.ts b/src/routes/[[actor=actor]]/(pages)/+layout.server.ts index b26f9d4..5d4f7d1 100644 --- a/src/routes/[[actor=actor]]/(pages)/+layout.server.ts +++ b/src/routes/[[actor=actor]]/(pages)/+layout.server.ts @@ -15,5 +15,5 @@ export async function load({ params, platform, request }) { throw error(404, 'Page not found'); } - return await loadData(actor, cache, false, params.page, env, platform); + return await loadData(actor, cache, params.page, env, platform); } diff --git a/src/routes/[[actor=actor]]/.well-known/site.standard.publication/+server.ts b/src/routes/[[actor=actor]]/.well-known/site.standard.publication/+server.ts index aed7c2c..b1d75ec 100644 --- a/src/routes/[[actor=actor]]/.well-known/site.standard.publication/+server.ts +++ b/src/routes/[[actor=actor]]/.well-known/site.standard.publication/+server.ts @@ -14,7 +14,7 @@ export async function GET({ params, platform, request }) { throw error(404, 'Page not found'); } - const data = await loadData(actor, cache, false, params.page, env, platform); + const data = await loadData(actor, cache, params.page, env, platform); if (!data.publication) throw error(300); -- 2.51.2 From 4acdb08ec98fe54499ad100eda0b12ec3cb0c08f Mon Sep 17 00:00:00 2001 From: Florian <45694132+flo-bit@users.noreply.github.com> Date: Wed, 15 Apr 2026 23:59:11 +0200 Subject: [PATCH 5/7] add kvs and stuff --- wrangler.jsonc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/wrangler.jsonc b/wrangler.jsonc index 765b915..d9d39bb 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -49,18 +49,19 @@ }, { "binding": "OAUTH_SESSIONS", - "id": "REPLACE_WITH_KV_NAMESPACE_ID" + "id": "4d872b4630a042d69ecd63f87d6b2094" }, { "binding": "OAUTH_STATES", - "id": "REPLACE_WITH_KV_NAMESPACE_ID" + "id": "c644b498d4af4c5892b3106d52c7760a" } ], "d1_databases": [ { "binding": "DB", "database_name": "blento", - "database_id": "922639e7-6321-42c8-a4bd-cdf48428fdac" + "database_id": "922639e7-6321-42c8-a4bd-cdf48428fdac", + "remote": true } ], "triggers": { -- 2.51.2 From c369d175439163bd116a689668a84909916b5590 Mon Sep 17 00:00:00 2001 From: Florian <45694132+flo-bit@users.noreply.github.com> Date: Thu, 16 Apr 2026 00:03:14 +0200 Subject: [PATCH 6/7] add tsx, format --- eslint.config.js | 1 + lex.config.js | 38 +- .../app/blento/card/getRecord.json | 198 ++++--- .../app/blento/card/listRecords.json | 481 +++++++++--------- lexicons-generated/app/blento/getCursor.json | 50 +- .../app/blento/getOverview.json | 91 ++-- lexicons-generated/app/blento/getProfile.json | 136 +++-- .../app/blento/notifyOfUpdate.json | 111 ++-- .../app/blento/page/getRecord.json | 198 ++++--- .../app/blento/page/listRecords.json | 288 +++++------ lexicons/app/blento/card.json | 8 +- package.json | 1 + pnpm-lock.yaml | 156 +++--- src/lexicon-types/index.ts | 20 +- src/lexicon-types/types/app/blento/card.ts | 50 +- .../types/app/blento/card/getRecord.ts | 98 ++-- .../types/app/blento/card/listRecords.ts | 368 +++++++------- .../types/app/blento/getCursor.ts | 36 +- .../types/app/blento/getOverview.ts | 57 +-- .../types/app/blento/getProfile.ts | 66 ++- .../types/app/blento/notifyOfUpdate.ts | 95 ++-- src/lexicon-types/types/app/blento/page.ts | 58 +-- .../types/app/blento/page/getRecord.ts | 98 ++-- .../types/app/blento/page/listRecords.ts | 168 +++--- src/lib/website/EditableWebsite.svelte | 1 - src/lib/website/Profile.svelte | 6 +- 26 files changed, 1404 insertions(+), 1474 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index ddef03e..294382f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -9,6 +9,7 @@ const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url)); export default [ includeIgnoreFile(gitignorePath), + { ignores: ['src/lexicon-types/**', 'lexicons-generated/**'] }, js.configs.recommended, ...ts.configs.recommended, ...svelte.configs['flat/recommended'], diff --git a/lex.config.js b/lex.config.js index ed6769e..44743a3 100644 --- a/lex.config.js +++ b/lex.config.js @@ -1,22 +1,22 @@ -import { defineLexiconConfig } from "@atcute/lex-cli"; +import { defineLexiconConfig } from '@atcute/lex-cli'; export default defineLexiconConfig({ - files: ["lexicons/**/*.json", "lexicons-pulled/**/*.json", "lexicons-generated/**/*.json"], - outdir: "src/lexicon-types/", - imports: ["@atcute/atproto"], - pull: { - outdir: "lexicons-pulled/", - sources: [ - { - type: "atproto", - mode: "nsids", - nsids: [ - "app.blento.card", - "app.blento.page", - "app.bsky.actor.profile", - "site.standard.publication" - ], - }, - ], - }, + files: ['lexicons/**/*.json', 'lexicons-pulled/**/*.json', 'lexicons-generated/**/*.json'], + outdir: 'src/lexicon-types/', + imports: ['@atcute/atproto'], + pull: { + outdir: 'lexicons-pulled/', + sources: [ + { + type: 'atproto', + mode: 'nsids', + nsids: [ + 'app.blento.card', + 'app.blento.page', + 'app.bsky.actor.profile', + 'site.standard.publication' + ] + } + ] + } }); diff --git a/lexicons-generated/app/blento/card/getRecord.json b/lexicons-generated/app/blento/card/getRecord.json index 513d584..2e5a76c 100644 --- a/lexicons-generated/app/blento/card/getRecord.json +++ b/lexicons-generated/app/blento/card/getRecord.json @@ -1,106 +1,96 @@ { - "lexicon": 1, - "id": "app.blento.card.getRecord", - "defs": { - "main": { - "type": "query", - "description": "Get a single app.blento.card record by AT URI", - "parameters": { - "type": "params", - "required": [ - "uri" - ], - "properties": { - "uri": { - "type": "string", - "format": "at-uri", - "description": "AT URI of the record" - }, - "profiles": { - "type": "boolean", - "description": "Include profile + identity info keyed by DID" - } - } - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": [ - "uri", - "did", - "collection", - "rkey", - "time_us" - ], - "properties": { - "uri": { - "type": "string", - "format": "at-uri" - }, - "did": { - "type": "string", - "format": "did" - }, - "collection": { - "type": "string", - "format": "nsid" - }, - "rkey": { - "type": "string" - }, - "cid": { - "type": "string" - }, - "record": { - "type": "ref", - "ref": "app.blento.card#main" - }, - "time_us": { - "type": "integer" - }, - "profiles": { - "type": "array", - "items": { - "type": "ref", - "ref": "#profileEntry" - } - } - } - } - } - }, - "profileEntry": { - "type": "object", - "required": [ - "did" - ], - "properties": { - "did": { - "type": "string", - "format": "did" - }, - "handle": { - "type": "string" - }, - "uri": { - "type": "string", - "format": "at-uri" - }, - "collection": { - "type": "string", - "format": "nsid" - }, - "rkey": { - "type": "string" - }, - "cid": { - "type": "string" - }, - "record": { - "type": "unknown" - } - } - } - } + "lexicon": 1, + "id": "app.blento.card.getRecord", + "defs": { + "main": { + "type": "query", + "description": "Get a single app.blento.card record by AT URI", + "parameters": { + "type": "params", + "required": ["uri"], + "properties": { + "uri": { + "type": "string", + "format": "at-uri", + "description": "AT URI of the record" + }, + "profiles": { + "type": "boolean", + "description": "Include profile + identity info keyed by DID" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["uri", "did", "collection", "rkey", "time_us"], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "cid": { + "type": "string" + }, + "record": { + "type": "ref", + "ref": "app.blento.card#main" + }, + "time_us": { + "type": "integer" + }, + "profiles": { + "type": "array", + "items": { + "type": "ref", + "ref": "#profileEntry" + } + } + } + } + } + }, + "profileEntry": { + "type": "object", + "required": ["did"], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "cid": { + "type": "string" + }, + "record": { + "type": "unknown" + } + } + } + } } diff --git a/lexicons-generated/app/blento/card/listRecords.json b/lexicons-generated/app/blento/card/listRecords.json index 415ce01..2e657ea 100644 --- a/lexicons-generated/app/blento/card/listRecords.json +++ b/lexicons-generated/app/blento/card/listRecords.json @@ -1,249 +1,236 @@ { - "lexicon": 1, - "id": "app.blento.card.listRecords", - "defs": { - "main": { - "type": "query", - "description": "Query app.blento.card records with filters", - "parameters": { - "type": "params", - "properties": { - "limit": { - "type": "integer", - "minimum": 1, - "maximum": 200, - "default": 50 - }, - "cursor": { - "type": "string" - }, - "actor": { - "type": "string", - "format": "at-identifier", - "description": "Filter by DID or handle (triggers on-demand backfill)" - }, - "profiles": { - "type": "boolean", - "description": "Include profile + identity info keyed by DID" - }, - "wMin": { - "type": "string", - "description": "Minimum value for w" - }, - "wMax": { - "type": "string", - "description": "Maximum value for w" - }, - "hMin": { - "type": "string", - "description": "Minimum value for h" - }, - "hMax": { - "type": "string", - "description": "Maximum value for h" - }, - "xMin": { - "type": "string", - "description": "Minimum value for x" - }, - "xMax": { - "type": "string", - "description": "Maximum value for x" - }, - "yMin": { - "type": "string", - "description": "Minimum value for y" - }, - "yMax": { - "type": "string", - "description": "Maximum value for y" - }, - "mobileWMin": { - "type": "string", - "description": "Minimum value for mobileW" - }, - "mobileWMax": { - "type": "string", - "description": "Maximum value for mobileW" - }, - "mobileHMin": { - "type": "string", - "description": "Minimum value for mobileH" - }, - "mobileHMax": { - "type": "string", - "description": "Maximum value for mobileH" - }, - "mobileXMin": { - "type": "string", - "description": "Minimum value for mobileX" - }, - "mobileXMax": { - "type": "string", - "description": "Maximum value for mobileX" - }, - "mobileYMin": { - "type": "string", - "description": "Minimum value for mobileY" - }, - "mobileYMax": { - "type": "string", - "description": "Maximum value for mobileY" - }, - "cardType": { - "type": "string", - "description": "Filter by cardType" - }, - "color": { - "type": "string", - "description": "Filter by color" - }, - "page": { - "type": "string", - "description": "Filter by page" - }, - "updatedAtMin": { - "type": "string", - "description": "Minimum value for updatedAt" - }, - "updatedAtMax": { - "type": "string", - "description": "Maximum value for updatedAt" - }, - "versionMin": { - "type": "string", - "description": "Minimum value for version" - }, - "versionMax": { - "type": "string", - "description": "Maximum value for version" - }, - "sort": { - "type": "string", - "knownValues": [ - "w", - "h", - "x", - "y", - "mobileW", - "mobileH", - "mobileX", - "mobileY", - "cardType", - "color", - "page", - "updatedAt", - "version" - ], - "description": "Field to sort by (default: time_us)" - }, - "order": { - "type": "string", - "knownValues": [ - "asc", - "desc" - ], - "description": "Sort direction (default: desc for dates/numbers/counts, asc for strings)" - } - } - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": [ - "records" - ], - "properties": { - "records": { - "type": "array", - "items": { - "type": "ref", - "ref": "#record" - } - }, - "cursor": { - "type": "string" - }, - "profiles": { - "type": "array", - "items": { - "type": "ref", - "ref": "#profileEntry" - } - } - } - } - } - }, - "record": { - "type": "object", - "required": [ - "uri", - "did", - "collection", - "rkey", - "time_us" - ], - "properties": { - "uri": { - "type": "string", - "format": "at-uri" - }, - "did": { - "type": "string", - "format": "did" - }, - "collection": { - "type": "string", - "format": "nsid" - }, - "rkey": { - "type": "string" - }, - "cid": { - "type": "string" - }, - "record": { - "type": "ref", - "ref": "app.blento.card#main" - }, - "time_us": { - "type": "integer" - } - } - }, - "profileEntry": { - "type": "object", - "required": [ - "did" - ], - "properties": { - "did": { - "type": "string", - "format": "did" - }, - "handle": { - "type": "string" - }, - "uri": { - "type": "string", - "format": "at-uri" - }, - "collection": { - "type": "string", - "format": "nsid" - }, - "rkey": { - "type": "string" - }, - "cid": { - "type": "string" - }, - "record": { - "type": "unknown" - } - } - } - } + "lexicon": 1, + "id": "app.blento.card.listRecords", + "defs": { + "main": { + "type": "query", + "description": "Query app.blento.card records with filters", + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 200, + "default": 50 + }, + "cursor": { + "type": "string" + }, + "actor": { + "type": "string", + "format": "at-identifier", + "description": "Filter by DID or handle (triggers on-demand backfill)" + }, + "profiles": { + "type": "boolean", + "description": "Include profile + identity info keyed by DID" + }, + "wMin": { + "type": "string", + "description": "Minimum value for w" + }, + "wMax": { + "type": "string", + "description": "Maximum value for w" + }, + "hMin": { + "type": "string", + "description": "Minimum value for h" + }, + "hMax": { + "type": "string", + "description": "Maximum value for h" + }, + "xMin": { + "type": "string", + "description": "Minimum value for x" + }, + "xMax": { + "type": "string", + "description": "Maximum value for x" + }, + "yMin": { + "type": "string", + "description": "Minimum value for y" + }, + "yMax": { + "type": "string", + "description": "Maximum value for y" + }, + "mobileWMin": { + "type": "string", + "description": "Minimum value for mobileW" + }, + "mobileWMax": { + "type": "string", + "description": "Maximum value for mobileW" + }, + "mobileHMin": { + "type": "string", + "description": "Minimum value for mobileH" + }, + "mobileHMax": { + "type": "string", + "description": "Maximum value for mobileH" + }, + "mobileXMin": { + "type": "string", + "description": "Minimum value for mobileX" + }, + "mobileXMax": { + "type": "string", + "description": "Maximum value for mobileX" + }, + "mobileYMin": { + "type": "string", + "description": "Minimum value for mobileY" + }, + "mobileYMax": { + "type": "string", + "description": "Maximum value for mobileY" + }, + "cardType": { + "type": "string", + "description": "Filter by cardType" + }, + "color": { + "type": "string", + "description": "Filter by color" + }, + "page": { + "type": "string", + "description": "Filter by page" + }, + "updatedAtMin": { + "type": "string", + "description": "Minimum value for updatedAt" + }, + "updatedAtMax": { + "type": "string", + "description": "Maximum value for updatedAt" + }, + "versionMin": { + "type": "string", + "description": "Minimum value for version" + }, + "versionMax": { + "type": "string", + "description": "Maximum value for version" + }, + "sort": { + "type": "string", + "knownValues": [ + "w", + "h", + "x", + "y", + "mobileW", + "mobileH", + "mobileX", + "mobileY", + "cardType", + "color", + "page", + "updatedAt", + "version" + ], + "description": "Field to sort by (default: time_us)" + }, + "order": { + "type": "string", + "knownValues": ["asc", "desc"], + "description": "Sort direction (default: desc for dates/numbers/counts, asc for strings)" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["records"], + "properties": { + "records": { + "type": "array", + "items": { + "type": "ref", + "ref": "#record" + } + }, + "cursor": { + "type": "string" + }, + "profiles": { + "type": "array", + "items": { + "type": "ref", + "ref": "#profileEntry" + } + } + } + } + } + }, + "record": { + "type": "object", + "required": ["uri", "did", "collection", "rkey", "time_us"], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "cid": { + "type": "string" + }, + "record": { + "type": "ref", + "ref": "app.blento.card#main" + }, + "time_us": { + "type": "integer" + } + } + }, + "profileEntry": { + "type": "object", + "required": ["did"], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "cid": { + "type": "string" + }, + "record": { + "type": "unknown" + } + } + } + } } diff --git a/lexicons-generated/app/blento/getCursor.json b/lexicons-generated/app/blento/getCursor.json index 555c126..a94e9f6 100644 --- a/lexicons-generated/app/blento/getCursor.json +++ b/lexicons-generated/app/blento/getCursor.json @@ -1,27 +1,27 @@ { - "lexicon": 1, - "id": "app.blento.getCursor", - "defs": { - "main": { - "type": "query", - "description": "Get the current cursor position", - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "properties": { - "time_us": { - "type": "integer" - }, - "date": { - "type": "string" - }, - "seconds_ago": { - "type": "integer" - } - } - } - } - } - } + "lexicon": 1, + "id": "app.blento.getCursor", + "defs": { + "main": { + "type": "query", + "description": "Get the current cursor position", + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "properties": { + "time_us": { + "type": "integer" + }, + "date": { + "type": "string" + }, + "seconds_ago": { + "type": "integer" + } + } + } + } + } + } } diff --git a/lexicons-generated/app/blento/getOverview.json b/lexicons-generated/app/blento/getOverview.json index 02050d6..015c067 100644 --- a/lexicons-generated/app/blento/getOverview.json +++ b/lexicons-generated/app/blento/getOverview.json @@ -1,51 +1,44 @@ { - "lexicon": 1, - "id": "app.blento.getOverview", - "defs": { - "main": { - "type": "query", - "description": "Get an overview of all indexed collections", - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": [ - "total_records", - "collections" - ], - "properties": { - "total_records": { - "type": "integer" - }, - "collections": { - "type": "array", - "items": { - "type": "ref", - "ref": "#collectionStats" - } - } - } - } - } - }, - "collectionStats": { - "type": "object", - "required": [ - "collection", - "records", - "unique_users" - ], - "properties": { - "collection": { - "type": "string" - }, - "records": { - "type": "integer" - }, - "unique_users": { - "type": "integer" - } - } - } - } + "lexicon": 1, + "id": "app.blento.getOverview", + "defs": { + "main": { + "type": "query", + "description": "Get an overview of all indexed collections", + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["total_records", "collections"], + "properties": { + "total_records": { + "type": "integer" + }, + "collections": { + "type": "array", + "items": { + "type": "ref", + "ref": "#collectionStats" + } + } + } + } + } + }, + "collectionStats": { + "type": "object", + "required": ["collection", "records", "unique_users"], + "properties": { + "collection": { + "type": "string" + }, + "records": { + "type": "integer" + }, + "unique_users": { + "type": "integer" + } + } + } + } } diff --git a/lexicons-generated/app/blento/getProfile.json b/lexicons-generated/app/blento/getProfile.json index e1a4ee7..22e735d 100644 --- a/lexicons-generated/app/blento/getProfile.json +++ b/lexicons-generated/app/blento/getProfile.json @@ -1,73 +1,67 @@ { - "lexicon": 1, - "id": "app.blento.getProfile", - "defs": { - "main": { - "type": "query", - "description": "Get a user's profiles by DID or handle", - "parameters": { - "type": "params", - "required": [ - "actor" - ], - "properties": { - "actor": { - "type": "string", - "format": "at-identifier", - "description": "DID or handle of the user" - } - } - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": [ - "profiles" - ], - "properties": { - "profiles": { - "type": "array", - "items": { - "type": "ref", - "ref": "#profileEntry" - } - } - } - } - } - }, - "profileEntry": { - "type": "object", - "required": [ - "did" - ], - "properties": { - "did": { - "type": "string", - "format": "did" - }, - "handle": { - "type": "string" - }, - "uri": { - "type": "string", - "format": "at-uri" - }, - "collection": { - "type": "string", - "format": "nsid" - }, - "rkey": { - "type": "string" - }, - "cid": { - "type": "string" - }, - "record": { - "type": "unknown" - } - } - } - } + "lexicon": 1, + "id": "app.blento.getProfile", + "defs": { + "main": { + "type": "query", + "description": "Get a user's profiles by DID or handle", + "parameters": { + "type": "params", + "required": ["actor"], + "properties": { + "actor": { + "type": "string", + "format": "at-identifier", + "description": "DID or handle of the user" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["profiles"], + "properties": { + "profiles": { + "type": "array", + "items": { + "type": "ref", + "ref": "#profileEntry" + } + } + } + } + } + }, + "profileEntry": { + "type": "object", + "required": ["did"], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "cid": { + "type": "string" + }, + "record": { + "type": "unknown" + } + } + } + } } diff --git a/lexicons-generated/app/blento/notifyOfUpdate.json b/lexicons-generated/app/blento/notifyOfUpdate.json index 08382b7..4c4b923 100644 --- a/lexicons-generated/app/blento/notifyOfUpdate.json +++ b/lexicons-generated/app/blento/notifyOfUpdate.json @@ -1,59 +1,56 @@ { - "lexicon": 1, - "id": "app.blento.notifyOfUpdate", - "defs": { - "main": { - "type": "procedure", - "description": "Notify of a record change for immediate indexing. Fetches the record from the user's PDS and indexes (or deletes) it.", - "input": { - "encoding": "application/json", - "schema": { - "type": "object", - "properties": { - "uri": { - "type": "string", - "format": "at-uri", - "description": "Single AT URI to fetch and index" - }, - "uris": { - "type": "array", - "items": { - "type": "string", - "format": "at-uri" - }, - "maxLength": 25, - "description": "Batch of AT URIs to fetch and index (max 25)" - } - } - } - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": [ - "indexed", - "deleted" - ], - "properties": { - "indexed": { - "type": "integer", - "description": "Number of records created or updated" - }, - "deleted": { - "type": "integer", - "description": "Number of records deleted (not found on PDS)" - }, - "errors": { - "type": "array", - "items": { - "type": "string" - }, - "description": "Errors for individual URIs that could not be processed" - } - } - } - } - } - } + "lexicon": 1, + "id": "app.blento.notifyOfUpdate", + "defs": { + "main": { + "type": "procedure", + "description": "Notify of a record change for immediate indexing. Fetches the record from the user's PDS and indexes (or deletes) it.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "properties": { + "uri": { + "type": "string", + "format": "at-uri", + "description": "Single AT URI to fetch and index" + }, + "uris": { + "type": "array", + "items": { + "type": "string", + "format": "at-uri" + }, + "maxLength": 25, + "description": "Batch of AT URIs to fetch and index (max 25)" + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["indexed", "deleted"], + "properties": { + "indexed": { + "type": "integer", + "description": "Number of records created or updated" + }, + "deleted": { + "type": "integer", + "description": "Number of records deleted (not found on PDS)" + }, + "errors": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Errors for individual URIs that could not be processed" + } + } + } + } + } + } } diff --git a/lexicons-generated/app/blento/page/getRecord.json b/lexicons-generated/app/blento/page/getRecord.json index 8d5c65f..e6b996c 100644 --- a/lexicons-generated/app/blento/page/getRecord.json +++ b/lexicons-generated/app/blento/page/getRecord.json @@ -1,106 +1,96 @@ { - "lexicon": 1, - "id": "app.blento.page.getRecord", - "defs": { - "main": { - "type": "query", - "description": "Get a single app.blento.page record by AT URI", - "parameters": { - "type": "params", - "required": [ - "uri" - ], - "properties": { - "uri": { - "type": "string", - "format": "at-uri", - "description": "AT URI of the record" - }, - "profiles": { - "type": "boolean", - "description": "Include profile + identity info keyed by DID" - } - } - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": [ - "uri", - "did", - "collection", - "rkey", - "time_us" - ], - "properties": { - "uri": { - "type": "string", - "format": "at-uri" - }, - "did": { - "type": "string", - "format": "did" - }, - "collection": { - "type": "string", - "format": "nsid" - }, - "rkey": { - "type": "string" - }, - "cid": { - "type": "string" - }, - "record": { - "type": "ref", - "ref": "app.blento.page#main" - }, - "time_us": { - "type": "integer" - }, - "profiles": { - "type": "array", - "items": { - "type": "ref", - "ref": "#profileEntry" - } - } - } - } - } - }, - "profileEntry": { - "type": "object", - "required": [ - "did" - ], - "properties": { - "did": { - "type": "string", - "format": "did" - }, - "handle": { - "type": "string" - }, - "uri": { - "type": "string", - "format": "at-uri" - }, - "collection": { - "type": "string", - "format": "nsid" - }, - "rkey": { - "type": "string" - }, - "cid": { - "type": "string" - }, - "record": { - "type": "unknown" - } - } - } - } + "lexicon": 1, + "id": "app.blento.page.getRecord", + "defs": { + "main": { + "type": "query", + "description": "Get a single app.blento.page record by AT URI", + "parameters": { + "type": "params", + "required": ["uri"], + "properties": { + "uri": { + "type": "string", + "format": "at-uri", + "description": "AT URI of the record" + }, + "profiles": { + "type": "boolean", + "description": "Include profile + identity info keyed by DID" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["uri", "did", "collection", "rkey", "time_us"], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "cid": { + "type": "string" + }, + "record": { + "type": "ref", + "ref": "app.blento.page#main" + }, + "time_us": { + "type": "integer" + }, + "profiles": { + "type": "array", + "items": { + "type": "ref", + "ref": "#profileEntry" + } + } + } + } + } + }, + "profileEntry": { + "type": "object", + "required": ["did"], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "cid": { + "type": "string" + }, + "record": { + "type": "unknown" + } + } + } + } } diff --git a/lexicons-generated/app/blento/page/listRecords.json b/lexicons-generated/app/blento/page/listRecords.json index fcc5845..9127056 100644 --- a/lexicons-generated/app/blento/page/listRecords.json +++ b/lexicons-generated/app/blento/page/listRecords.json @@ -1,154 +1,138 @@ { - "lexicon": 1, - "id": "app.blento.page.listRecords", - "defs": { - "main": { - "type": "query", - "description": "Query app.blento.page records with filters", - "parameters": { - "type": "params", - "properties": { - "limit": { - "type": "integer", - "minimum": 1, - "maximum": 200, - "default": 50 - }, - "cursor": { - "type": "string" - }, - "actor": { - "type": "string", - "format": "at-identifier", - "description": "Filter by DID or handle (triggers on-demand backfill)" - }, - "profiles": { - "type": "boolean", - "description": "Include profile + identity info keyed by DID" - }, - "name": { - "type": "string", - "description": "Filter by name" - }, - "description": { - "type": "string", - "description": "Filter by description" - }, - "sort": { - "type": "string", - "knownValues": [ - "name", - "description" - ], - "description": "Field to sort by (default: time_us)" - }, - "order": { - "type": "string", - "knownValues": [ - "asc", - "desc" - ], - "description": "Sort direction (default: desc for dates/numbers/counts, asc for strings)" - } - } - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": [ - "records" - ], - "properties": { - "records": { - "type": "array", - "items": { - "type": "ref", - "ref": "#record" - } - }, - "cursor": { - "type": "string" - }, - "profiles": { - "type": "array", - "items": { - "type": "ref", - "ref": "#profileEntry" - } - } - } - } - } - }, - "record": { - "type": "object", - "required": [ - "uri", - "did", - "collection", - "rkey", - "time_us" - ], - "properties": { - "uri": { - "type": "string", - "format": "at-uri" - }, - "did": { - "type": "string", - "format": "did" - }, - "collection": { - "type": "string", - "format": "nsid" - }, - "rkey": { - "type": "string" - }, - "cid": { - "type": "string" - }, - "record": { - "type": "ref", - "ref": "app.blento.page#main" - }, - "time_us": { - "type": "integer" - } - } - }, - "profileEntry": { - "type": "object", - "required": [ - "did" - ], - "properties": { - "did": { - "type": "string", - "format": "did" - }, - "handle": { - "type": "string" - }, - "uri": { - "type": "string", - "format": "at-uri" - }, - "collection": { - "type": "string", - "format": "nsid" - }, - "rkey": { - "type": "string" - }, - "cid": { - "type": "string" - }, - "record": { - "type": "unknown" - } - } - } - } + "lexicon": 1, + "id": "app.blento.page.listRecords", + "defs": { + "main": { + "type": "query", + "description": "Query app.blento.page records with filters", + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 200, + "default": 50 + }, + "cursor": { + "type": "string" + }, + "actor": { + "type": "string", + "format": "at-identifier", + "description": "Filter by DID or handle (triggers on-demand backfill)" + }, + "profiles": { + "type": "boolean", + "description": "Include profile + identity info keyed by DID" + }, + "name": { + "type": "string", + "description": "Filter by name" + }, + "description": { + "type": "string", + "description": "Filter by description" + }, + "sort": { + "type": "string", + "knownValues": ["name", "description"], + "description": "Field to sort by (default: time_us)" + }, + "order": { + "type": "string", + "knownValues": ["asc", "desc"], + "description": "Sort direction (default: desc for dates/numbers/counts, asc for strings)" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["records"], + "properties": { + "records": { + "type": "array", + "items": { + "type": "ref", + "ref": "#record" + } + }, + "cursor": { + "type": "string" + }, + "profiles": { + "type": "array", + "items": { + "type": "ref", + "ref": "#profileEntry" + } + } + } + } + } + }, + "record": { + "type": "object", + "required": ["uri", "did", "collection", "rkey", "time_us"], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "cid": { + "type": "string" + }, + "record": { + "type": "ref", + "ref": "app.blento.page#main" + }, + "time_us": { + "type": "integer" + } + } + }, + "profileEntry": { + "type": "object", + "required": ["did"], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "cid": { + "type": "string" + }, + "record": { + "type": "unknown" + } + } + } + } } diff --git a/lexicons/app/blento/card.json b/lexicons/app/blento/card.json index 53d3424..bcee074 100644 --- a/lexicons/app/blento/card.json +++ b/lexicons/app/blento/card.json @@ -8,13 +8,7 @@ "key": "tid", "record": { "type": "object", - "required": [ - "w", - "h", - "x", - "y", - "cardType" - ], + "required": ["w", "h", "x", "y", "cardType"], "properties": { "w": { "type": "integer" }, "h": { "type": "integer" }, diff --git a/package.json b/package.json index c095117..f7e1d35 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "svelte-check": "^4.4.5", "svelte-maplibre-gl": "^1.0.3", "tailwindcss": "^4.2.1", + "tsx": "^4.21.0", "typescript": "^5.9.3", "typescript-eslint": "^8.57.0", "valibot": "^1.3.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 24a43b6..f4e8738 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -49,28 +49,28 @@ importers: version: 4.20260313.1 '@ethercorps/sveltekit-og': specifier: ^4.2.1 - version: 4.2.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1))) + version: 4.2.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))) '@floating-ui/dom': specifier: ^1.7.6 version: 1.7.6 '@foxui/3d': specifier: ^0.8.0 - version: 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) + version: 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) '@foxui/colors': specifier: ^0.8.0 - version: 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) + version: 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) '@foxui/core': specifier: ^0.8.0 - version: 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) + version: 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) '@foxui/social': specifier: ^0.8.0 - version: 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(@tiptap/extension-code-block@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))(@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))(highlight.js@11.11.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) + version: 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(@tiptap/extension-code-block@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))(@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))(highlight.js@11.11.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) '@foxui/time': specifier: ^0.8.0 - version: 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) + version: 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) '@foxui/visual': specifier: ^0.8.0 - version: 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) + version: 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) '@internationalized/date': specifier: ^3.12.0 version: 3.12.0 @@ -136,7 +136,7 @@ importers: version: 0.183.1 bits-ui: specifier: ^2.16.3 - version: 2.16.3(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11) + version: 2.16.3(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11) clsx: specifier: ^2.1.1 version: 2.1.1 @@ -221,19 +221,19 @@ importers: version: 10.0.1(eslint@10.0.3(jiti@2.6.1)) '@sveltejs/adapter-cloudflare': specifier: ^7.2.8 - version: 7.2.8(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(wrangler@4.73.0(@cloudflare/workers-types@4.20260313.1)) + version: 7.2.8(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(wrangler@4.73.0(@cloudflare/workers-types@4.20260313.1)) '@sveltejs/kit': specifier: ^2.55.0 - version: 2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)) + version: 2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)) '@sveltejs/vite-plugin-svelte': specifier: ^7.0.0 - version: 7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)) + version: 7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)) '@tailwindcss/forms': specifier: ^0.5.11 version: 0.5.11(tailwindcss@4.2.1) '@tailwindcss/vite': specifier: ^4.2.1 - version: 4.2.1(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)) + version: 4.2.1(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)) '@types/turndown': specifier: ^5.0.6 version: 5.0.6 @@ -270,6 +270,9 @@ importers: tailwindcss: specifier: ^4.2.1 version: 4.2.1 + tsx: + specifier: ^4.21.0 + version: 4.21.0 typescript: specifier: ^5.9.3 version: 5.9.3 @@ -281,10 +284,10 @@ importers: version: 1.3.1(typescript@5.9.3) vite: specifier: ^8.0.0 - version: 8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1) + version: 8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0) vitest: specifier: ^4.1.4 - version: 4.1.4(@types/node@25.0.10)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)) + version: 4.1.4(@types/node@25.0.10)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)) packages: @@ -2046,6 +2049,9 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + gl-matrix@3.4.4: resolution: {integrity: sha512-latSnyDNt/8zYUB6VIJ6PCh2jBjJX6gnDsoCZ7LyW7GkqrD51EWwa9qCoGixj8YqBtETQK/xY7OmpTF8xz1DdQ==} @@ -2761,6 +2767,9 @@ packages: resize-observer-polyfill@1.5.1: resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve-protobuf-schema@2.1.0: resolution: {integrity: sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==} @@ -3028,6 +3037,11 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + turndown@7.2.2: resolution: {integrity: sha512-1F7db8BiExOKxjSMU2b7if62D/XOyQyZbPKq/nUwopfgnHlqXHqQ0lvfUTeUIr1lZJzOPFn43dODyMSIfvWRKQ==} @@ -3695,10 +3709,10 @@ snapshots: '@eslint/core': 1.1.1 levn: 0.4.1 - '@ethercorps/sveltekit-og@4.2.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))': + '@ethercorps/sveltekit-og@4.2.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))': dependencies: '@resvg/resvg-wasm': 2.6.2 - '@sveltejs/kit': 2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)) + '@sveltejs/kit': 2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)) '@takumi-rs/helpers': 0.55.4 '@takumi-rs/image-response': 0.55.4 '@takumi-rs/wasm': 0.55.4 @@ -3718,13 +3732,13 @@ snapshots: '@floating-ui/utils@0.2.11': {} - '@foxui/3d@0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1)': + '@foxui/3d@0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1)': dependencies: - '@foxui/core': 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) + '@foxui/core': 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) '@threlte/core': 8.4.1(svelte@5.53.11)(three@0.183.2) '@threlte/extras': 9.8.1(@types/three@0.183.1)(svelte@5.53.11)(three@0.183.2) '@types/three': 0.183.1 - bits-ui: 2.16.3(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11) + bits-ui: 2.16.3(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11) svelte: 5.53.11 tailwindcss: 4.2.1 three: 0.183.2 @@ -3735,12 +3749,12 @@ snapshots: - react-dom - vue - '@foxui/colors@0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1)': + '@foxui/colors@0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1)': dependencies: - '@foxui/core': 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) + '@foxui/core': 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) '@texel/color': 1.1.11 '@use-gesture/vanilla': 10.3.1 - bits-ui: 2.16.3(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11) + bits-ui: 2.16.3(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11) svelte: 5.53.11 tailwindcss: 4.2.1 transitivePeerDependencies: @@ -3750,10 +3764,10 @@ snapshots: - react-dom - vue - '@foxui/core@0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1)': + '@foxui/core@0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1)': dependencies: '@number-flow/svelte': 0.4.0(svelte@5.53.11) - bits-ui: 2.16.3(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11) + bits-ui: 2.16.3(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11) clsx: 2.1.1 dompurify: 3.3.3 mode-watcher: 1.1.0(svelte@5.53.11) @@ -3770,15 +3784,15 @@ snapshots: - react-dom - vue - '@foxui/social@0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(@tiptap/extension-code-block@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))(@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))(highlight.js@11.11.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1)': + '@foxui/social@0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(@tiptap/extension-code-block@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))(@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))(highlight.js@11.11.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1)': dependencies: '@atcute/bluesky': 3.3.0 '@atcute/bluesky-richtext-segmenter': 3.0.0 - '@foxui/core': 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) - '@foxui/text': 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(@tiptap/extension-code-block@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))(@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))(highlight.js@11.11.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) - '@foxui/time': 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) + '@foxui/core': 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) + '@foxui/text': 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(@tiptap/extension-code-block@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))(@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))(highlight.js@11.11.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) + '@foxui/time': 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) '@use-gesture/vanilla': 10.3.1 - bits-ui: 2.16.3(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11) + bits-ui: 2.16.3(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11) emoji-picker-element: 1.29.1 hls.js: 1.6.15 is-emoji-supported: 0.0.5 @@ -3795,10 +3809,10 @@ snapshots: - react-dom - vue - '@foxui/text@0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(@tiptap/extension-code-block@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))(@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))(highlight.js@11.11.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1)': + '@foxui/text@0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(@tiptap/extension-code-block@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))(@tiptap/extensions@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))(highlight.js@11.11.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1)': dependencies: '@floating-ui/dom': 1.7.6 - '@foxui/core': 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) + '@foxui/core': 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) '@tiptap/core': 3.20.1(@tiptap/pm@3.20.1) '@tiptap/extension-bubble-menu': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) '@tiptap/extension-code-block-lowlight': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/extension-code-block@3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1)(highlight.js@11.11.1)(lowlight@3.3.0) @@ -3818,7 +3832,7 @@ snapshots: '@tiptap/pm': 3.20.1 '@tiptap/starter-kit': 3.20.1 '@tiptap/suggestion': 3.20.1(@tiptap/core@3.20.1(@tiptap/pm@3.20.1))(@tiptap/pm@3.20.1) - bits-ui: 2.16.3(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11) + bits-ui: 2.16.3(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11) lowlight: 3.3.0 state: link:@tiptap/pm/state svelte: 5.53.11 @@ -3834,11 +3848,11 @@ snapshots: - react-dom - vue - '@foxui/time@0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1)': + '@foxui/time@0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1)': dependencies: - '@foxui/core': 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) + '@foxui/core': 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) '@number-flow/svelte': 0.4.0(svelte@5.53.11) - bits-ui: 2.16.3(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11) + bits-ui: 2.16.3(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11) svelte: 5.53.11 tailwindcss: 4.2.1 transitivePeerDependencies: @@ -3848,11 +3862,11 @@ snapshots: - react-dom - vue - '@foxui/visual@0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1)': + '@foxui/visual@0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1)': dependencies: - '@foxui/colors': 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) - '@foxui/core': 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) - bits-ui: 2.16.3(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11) + '@foxui/colors': 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) + '@foxui/core': 0.8.0(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(svelte@5.53.11)(tailwindcss@4.2.1) + bits-ui: 2.16.3(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11) canvas-confetti: 1.9.4 cheerio: 1.2.0 svelte: 5.53.11 @@ -4160,18 +4174,18 @@ snapshots: dependencies: acorn: 8.16.0 - '@sveltejs/adapter-cloudflare@7.2.8(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(wrangler@4.73.0(@cloudflare/workers-types@4.20260313.1))': + '@sveltejs/adapter-cloudflare@7.2.8(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(wrangler@4.73.0(@cloudflare/workers-types@4.20260313.1))': dependencies: '@cloudflare/workers-types': 4.20260313.1 - '@sveltejs/kit': 2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)) + '@sveltejs/kit': 2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)) worktop: 0.8.0-next.18 wrangler: 4.73.0(@cloudflare/workers-types@4.20260313.1) - '@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1))': + '@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))': dependencies: '@standard-schema/spec': 1.1.0 '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) - '@sveltejs/vite-plugin-svelte': 7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)) + '@sveltejs/vite-plugin-svelte': 7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)) '@types/cookie': 0.6.0 acorn: 8.16.0 cookie: 0.6.0 @@ -4183,18 +4197,18 @@ snapshots: set-cookie-parser: 3.0.1 sirv: 3.0.2 svelte: 5.53.11 - vite: 8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1) + vite: 8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0) optionalDependencies: typescript: 5.9.3 - '@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1))': + '@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))': dependencies: deepmerge: 4.3.1 magic-string: 0.30.21 obug: 2.1.1 svelte: 5.53.11 - vite: 8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1) - vitefu: 1.1.2(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)) + vite: 8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0) + vitefu: 1.1.2(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)) '@swc/helpers@0.5.19': dependencies: @@ -4271,12 +4285,12 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 4.2.1 - '@tailwindcss/vite@4.2.1(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1))': + '@tailwindcss/vite@4.2.1(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))': dependencies: '@tailwindcss/node': 4.2.1 '@tailwindcss/oxide': 4.2.1 tailwindcss: 4.2.1 - vite: 8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1) + vite: 8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0) '@takumi-rs/core-darwin-arm64@0.55.4': optional: true @@ -4720,13 +4734,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.4(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1))': + '@vitest/mocker@4.1.4(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0))': dependencies: '@vitest/spy': 4.1.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1) + vite: 8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0) '@vitest/pretty-format@4.1.4': dependencies: @@ -4783,15 +4797,15 @@ snapshots: dependencies: require-from-string: 2.0.2 - bits-ui@2.16.3(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11): + bits-ui@2.16.3(@internationalized/date@3.12.0)(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11): dependencies: '@floating-ui/core': 1.7.5 '@floating-ui/dom': 1.7.6 '@internationalized/date': 3.12.0 esm-env: 1.2.2 - runed: 0.35.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11) + runed: 0.35.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11) svelte: 5.53.11 - svelte-toolbelt: 0.10.6(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11) + svelte-toolbelt: 0.10.6(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11) tabbable: 6.4.0 transitivePeerDependencies: - '@sveltejs/kit' @@ -5158,6 +5172,10 @@ snapshots: fsevents@2.3.3: optional: true + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + gl-matrix@3.4.4: {} glob-parent@6.0.2: @@ -5795,6 +5813,8 @@ snapshots: resize-observer-polyfill@1.5.1: {} + resolve-pkg-maps@1.0.0: {} + resolve-protobuf-schema@2.1.0: dependencies: protocol-buffers-schema: 3.6.0 @@ -5837,14 +5857,14 @@ snapshots: esm-env: 1.2.2 svelte: 5.53.11 - runed@0.35.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11): + runed@0.35.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11): dependencies: dequal: 2.0.3 esm-env: 1.2.2 lz-string: 1.5.0 svelte: 5.53.11 optionalDependencies: - '@sveltejs/kit': 2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)) + '@sveltejs/kit': 2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)) rw@1.3.3: {} @@ -5989,10 +6009,10 @@ snapshots: '@tiptap/pm': 3.20.1 svelte: 5.53.11 - svelte-toolbelt@0.10.6(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11): + svelte-toolbelt@0.10.6(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11): dependencies: clsx: 2.1.1 - runed: 0.35.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)))(svelte@5.53.11) + runed: 0.35.1(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@7.0.0(svelte@5.53.11)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11)(typescript@5.9.3)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)))(svelte@5.53.11) style-to-object: 1.0.14 svelte: 5.53.11 transitivePeerDependencies: @@ -6100,6 +6120,13 @@ snapshots: tslib@2.8.1: {} + tsx@4.21.0: + dependencies: + esbuild: 0.27.3 + get-tsconfig: 4.14.0 + optionalDependencies: + fsevents: 2.3.3 + turndown@7.2.2: dependencies: '@mixmark-io/domino': 2.2.0 @@ -6170,7 +6197,7 @@ snapshots: optionalDependencies: typescript: 5.9.3 - vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1): + vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0): dependencies: '@oxc-project/runtime': 0.115.0 lightningcss: 1.32.0 @@ -6183,15 +6210,16 @@ snapshots: esbuild: 0.27.3 fsevents: 2.3.3 jiti: 2.6.1 + tsx: 4.21.0 - vitefu@1.1.2(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)): + vitefu@1.1.2(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)): optionalDependencies: - vite: 8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1) + vite: 8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0) - vitest@4.1.4(@types/node@25.0.10)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)): + vitest@4.1.4(@types/node@25.0.10)(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)): dependencies: '@vitest/expect': 4.1.4 - '@vitest/mocker': 4.1.4(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)) + '@vitest/mocker': 4.1.4(vite@8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0)) '@vitest/pretty-format': 4.1.4 '@vitest/runner': 4.1.4 '@vitest/snapshot': 4.1.4 @@ -6208,7 +6236,7 @@ snapshots: tinyexec: 1.1.1 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vite: 8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1) + vite: 8.0.0(@types/node@25.0.10)(esbuild@0.27.3)(jiti@2.6.1)(tsx@4.21.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 25.0.10 diff --git a/src/lexicon-types/index.ts b/src/lexicon-types/index.ts index 5f06e64..9d550a9 100644 --- a/src/lexicon-types/index.ts +++ b/src/lexicon-types/index.ts @@ -1,10 +1,10 @@ -export * as AppBlentoCard from "./types/app/blento/card.js"; -export * as AppBlentoCardGetRecord from "./types/app/blento/card/getRecord.js"; -export * as AppBlentoCardListRecords from "./types/app/blento/card/listRecords.js"; -export * as AppBlentoGetCursor from "./types/app/blento/getCursor.js"; -export * as AppBlentoGetOverview from "./types/app/blento/getOverview.js"; -export * as AppBlentoGetProfile from "./types/app/blento/getProfile.js"; -export * as AppBlentoNotifyOfUpdate from "./types/app/blento/notifyOfUpdate.js"; -export * as AppBlentoPage from "./types/app/blento/page.js"; -export * as AppBlentoPageGetRecord from "./types/app/blento/page/getRecord.js"; -export * as AppBlentoPageListRecords from "./types/app/blento/page/listRecords.js"; +export * as AppBlentoCard from './types/app/blento/card.js'; +export * as AppBlentoCardGetRecord from './types/app/blento/card/getRecord.js'; +export * as AppBlentoCardListRecords from './types/app/blento/card/listRecords.js'; +export * as AppBlentoGetCursor from './types/app/blento/getCursor.js'; +export * as AppBlentoGetOverview from './types/app/blento/getOverview.js'; +export * as AppBlentoGetProfile from './types/app/blento/getProfile.js'; +export * as AppBlentoNotifyOfUpdate from './types/app/blento/notifyOfUpdate.js'; +export * as AppBlentoPage from './types/app/blento/page.js'; +export * as AppBlentoPageGetRecord from './types/app/blento/page/getRecord.js'; +export * as AppBlentoPageListRecords from './types/app/blento/page/listRecords.js'; diff --git a/src/lexicon-types/types/app/blento/card.ts b/src/lexicon-types/types/app/blento/card.ts index 89f29cc..585271f 100644 --- a/src/lexicon-types/types/app/blento/card.ts +++ b/src/lexicon-types/types/app/blento/card.ts @@ -1,26 +1,26 @@ -import type {} from "@atcute/lexicons"; -import * as v from "@atcute/lexicons/validations"; -import type {} from "@atcute/lexicons/ambient"; +import type {} from '@atcute/lexicons'; +import * as v from '@atcute/lexicons/validations'; +import type {} from '@atcute/lexicons/ambient'; const _mainSchema = /*#__PURE__*/ v.record( - /*#__PURE__*/ v.tidString(), - /*#__PURE__*/ v.object({ - $type: /*#__PURE__*/ v.literal("app.blento.card"), - cardData: /*#__PURE__*/ v.unknown(), - cardType: /*#__PURE__*/ v.string(), - color: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - h: /*#__PURE__*/ v.integer(), - mobileH: /*#__PURE__*/ v.integer(), - mobileW: /*#__PURE__*/ v.integer(), - mobileX: /*#__PURE__*/ v.integer(), - mobileY: /*#__PURE__*/ v.integer(), - page: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - updatedAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), - version: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), - w: /*#__PURE__*/ v.integer(), - x: /*#__PURE__*/ v.integer(), - y: /*#__PURE__*/ v.integer(), - }), + /*#__PURE__*/ v.tidString(), + /*#__PURE__*/ v.object({ + $type: /*#__PURE__*/ v.literal('app.blento.card'), + cardData: /*#__PURE__*/ v.unknown(), + cardType: /*#__PURE__*/ v.string(), + color: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + h: /*#__PURE__*/ v.integer(), + mobileH: /*#__PURE__*/ v.integer(), + mobileW: /*#__PURE__*/ v.integer(), + mobileX: /*#__PURE__*/ v.integer(), + mobileY: /*#__PURE__*/ v.integer(), + page: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + updatedAt: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.datetimeString()), + version: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), + w: /*#__PURE__*/ v.integer(), + x: /*#__PURE__*/ v.integer(), + y: /*#__PURE__*/ v.integer() + }) ); type main$schematype = typeof _mainSchema; @@ -31,8 +31,8 @@ export const mainSchema = _mainSchema as mainSchema; export interface Main extends v.InferInput {} -declare module "@atcute/lexicons/ambient" { - interface Records { - "app.blento.card": mainSchema; - } +declare module '@atcute/lexicons/ambient' { + interface Records { + 'app.blento.card': mainSchema; + } } diff --git a/src/lexicon-types/types/app/blento/card/getRecord.ts b/src/lexicon-types/types/app/blento/card/getRecord.ts index 04bc2c7..3ad3987 100644 --- a/src/lexicon-types/types/app/blento/card/getRecord.ts +++ b/src/lexicon-types/types/app/blento/card/getRecord.ts @@ -1,50 +1,48 @@ -import type {} from "@atcute/lexicons"; -import * as v from "@atcute/lexicons/validations"; -import type {} from "@atcute/lexicons/ambient"; -import * as AppBlentoCard from "../card.js"; +import type {} from '@atcute/lexicons'; +import * as v from '@atcute/lexicons/validations'; +import type {} from '@atcute/lexicons/ambient'; +import * as AppBlentoCard from '../card.js'; -const _mainSchema = /*#__PURE__*/ v.query("app.blento.card.getRecord", { - params: /*#__PURE__*/ v.object({ - /** - * Include profile + identity info keyed by DID - */ - profiles: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), - /** - * AT URI of the record - */ - uri: /*#__PURE__*/ v.resourceUriString(), - }), - output: { - type: "lex", - schema: /*#__PURE__*/ v.object({ - cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - collection: /*#__PURE__*/ v.nsidString(), - did: /*#__PURE__*/ v.didString(), - get profiles() { - return /*#__PURE__*/ v.optional( - /*#__PURE__*/ v.array(profileEntrySchema), - ); - }, - get record() { - return /*#__PURE__*/ v.optional(AppBlentoCard.mainSchema); - }, - rkey: /*#__PURE__*/ v.string(), - time_us: /*#__PURE__*/ v.integer(), - uri: /*#__PURE__*/ v.resourceUriString(), - }), - }, +const _mainSchema = /*#__PURE__*/ v.query('app.blento.card.getRecord', { + params: /*#__PURE__*/ v.object({ + /** + * Include profile + identity info keyed by DID + */ + profiles: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), + /** + * AT URI of the record + */ + uri: /*#__PURE__*/ v.resourceUriString() + }), + output: { + type: 'lex', + schema: /*#__PURE__*/ v.object({ + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + collection: /*#__PURE__*/ v.nsidString(), + did: /*#__PURE__*/ v.didString(), + get profiles() { + return /*#__PURE__*/ v.optional(/*#__PURE__*/ v.array(profileEntrySchema)); + }, + get record() { + return /*#__PURE__*/ v.optional(AppBlentoCard.mainSchema); + }, + rkey: /*#__PURE__*/ v.string(), + time_us: /*#__PURE__*/ v.integer(), + uri: /*#__PURE__*/ v.resourceUriString() + }) + } }); const _profileEntrySchema = /*#__PURE__*/ v.object({ - $type: /*#__PURE__*/ v.optional( - /*#__PURE__*/ v.literal("app.blento.card.getRecord#profileEntry"), - ), - cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - collection: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.nsidString()), - did: /*#__PURE__*/ v.didString(), - handle: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - record: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.unknown()), - rkey: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), + $type: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.literal('app.blento.card.getRecord#profileEntry') + ), + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + collection: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.nsidString()), + did: /*#__PURE__*/ v.didString(), + handle: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + record: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.unknown()), + rkey: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()) }); type main$schematype = typeof _mainSchema; @@ -58,11 +56,11 @@ export const profileEntrySchema = _profileEntrySchema as profileEntrySchema; export interface ProfileEntry extends v.InferInput {} -export interface $params extends v.InferInput {} -export interface $output extends v.InferXRPCBodyInput {} +export interface $params extends v.InferInput {} +export interface $output extends v.InferXRPCBodyInput {} -declare module "@atcute/lexicons/ambient" { - interface XRPCQueries { - "app.blento.card.getRecord": mainSchema; - } +declare module '@atcute/lexicons/ambient' { + interface XRPCQueries { + 'app.blento.card.getRecord': mainSchema; + } } diff --git a/src/lexicon-types/types/app/blento/card/listRecords.ts b/src/lexicon-types/types/app/blento/card/listRecords.ts index efa75a6..db056ba 100644 --- a/src/lexicon-types/types/app/blento/card/listRecords.ts +++ b/src/lexicon-types/types/app/blento/card/listRecords.ts @@ -1,190 +1,182 @@ -import type {} from "@atcute/lexicons"; -import * as v from "@atcute/lexicons/validations"; -import type {} from "@atcute/lexicons/ambient"; -import * as AppBlentoCard from "../card.js"; +import type {} from '@atcute/lexicons'; +import * as v from '@atcute/lexicons/validations'; +import type {} from '@atcute/lexicons/ambient'; +import * as AppBlentoCard from '../card.js'; -const _mainSchema = /*#__PURE__*/ v.query("app.blento.card.listRecords", { - params: /*#__PURE__*/ v.object({ - /** - * Filter by DID or handle (triggers on-demand backfill) - */ - actor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.actorIdentifierString()), - /** - * Filter by cardType - */ - cardType: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * Filter by color - */ - color: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * Maximum value for h - */ - hMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * Minimum value for h - */ - hMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * @minimum 1 - * @maximum 200 - * @default 50 - */ - limit: /*#__PURE__*/ v.optional( - /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.integer(), [ - /*#__PURE__*/ v.integerRange(1, 200), - ]), - 50, - ), - /** - * Maximum value for mobileH - */ - mobileHMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * Minimum value for mobileH - */ - mobileHMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * Maximum value for mobileW - */ - mobileWMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * Minimum value for mobileW - */ - mobileWMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * Maximum value for mobileX - */ - mobileXMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * Minimum value for mobileX - */ - mobileXMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * Maximum value for mobileY - */ - mobileYMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * Minimum value for mobileY - */ - mobileYMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * Sort direction (default: desc for dates/numbers/counts, asc for strings) - */ - order: /*#__PURE__*/ v.optional( - /*#__PURE__*/ v.string<"asc" | "desc" | (string & {})>(), - ), - /** - * Filter by page - */ - page: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * Include profile + identity info keyed by DID - */ - profiles: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), - /** - * Field to sort by (default: time_us) - */ - sort: /*#__PURE__*/ v.optional( - /*#__PURE__*/ v.string< - | "cardType" - | "color" - | "h" - | "mobileH" - | "mobileW" - | "mobileX" - | "mobileY" - | "page" - | "updatedAt" - | "version" - | "w" - | "x" - | "y" - | (string & {}) - >(), - ), - /** - * Maximum value for updatedAt - */ - updatedAtMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * Minimum value for updatedAt - */ - updatedAtMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * Maximum value for version - */ - versionMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * Minimum value for version - */ - versionMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * Maximum value for w - */ - wMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * Minimum value for w - */ - wMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * Maximum value for x - */ - xMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * Minimum value for x - */ - xMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * Maximum value for y - */ - yMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * Minimum value for y - */ - yMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - }), - output: { - type: "lex", - schema: /*#__PURE__*/ v.object({ - cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - get profiles() { - return /*#__PURE__*/ v.optional( - /*#__PURE__*/ v.array(profileEntrySchema), - ); - }, - get records() { - return /*#__PURE__*/ v.array(recordSchema); - }, - }), - }, +const _mainSchema = /*#__PURE__*/ v.query('app.blento.card.listRecords', { + params: /*#__PURE__*/ v.object({ + /** + * Filter by DID or handle (triggers on-demand backfill) + */ + actor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.actorIdentifierString()), + /** + * Filter by cardType + */ + cardType: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Filter by color + */ + color: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Maximum value for h + */ + hMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Minimum value for h + */ + hMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * @minimum 1 + * @maximum 200 + * @default 50 + */ + limit: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.integer(), [/*#__PURE__*/ v.integerRange(1, 200)]), + 50 + ), + /** + * Maximum value for mobileH + */ + mobileHMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Minimum value for mobileH + */ + mobileHMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Maximum value for mobileW + */ + mobileWMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Minimum value for mobileW + */ + mobileWMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Maximum value for mobileX + */ + mobileXMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Minimum value for mobileX + */ + mobileXMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Maximum value for mobileY + */ + mobileYMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Minimum value for mobileY + */ + mobileYMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Sort direction (default: desc for dates/numbers/counts, asc for strings) + */ + order: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string<'asc' | 'desc' | (string & {})>()), + /** + * Filter by page + */ + page: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Include profile + identity info keyed by DID + */ + profiles: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), + /** + * Field to sort by (default: time_us) + */ + sort: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.string< + | 'cardType' + | 'color' + | 'h' + | 'mobileH' + | 'mobileW' + | 'mobileX' + | 'mobileY' + | 'page' + | 'updatedAt' + | 'version' + | 'w' + | 'x' + | 'y' + | (string & {}) + >() + ), + /** + * Maximum value for updatedAt + */ + updatedAtMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Minimum value for updatedAt + */ + updatedAtMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Maximum value for version + */ + versionMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Minimum value for version + */ + versionMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Maximum value for w + */ + wMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Minimum value for w + */ + wMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Maximum value for x + */ + xMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Minimum value for x + */ + xMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Maximum value for y + */ + yMax: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Minimum value for y + */ + yMin: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()) + }), + output: { + type: 'lex', + schema: /*#__PURE__*/ v.object({ + cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + get profiles() { + return /*#__PURE__*/ v.optional(/*#__PURE__*/ v.array(profileEntrySchema)); + }, + get records() { + return /*#__PURE__*/ v.array(recordSchema); + } + }) + } }); const _profileEntrySchema = /*#__PURE__*/ v.object({ - $type: /*#__PURE__*/ v.optional( - /*#__PURE__*/ v.literal("app.blento.card.listRecords#profileEntry"), - ), - cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - collection: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.nsidString()), - did: /*#__PURE__*/ v.didString(), - handle: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - record: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.unknown()), - rkey: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), + $type: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.literal('app.blento.card.listRecords#profileEntry') + ), + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + collection: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.nsidString()), + did: /*#__PURE__*/ v.didString(), + handle: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + record: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.unknown()), + rkey: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()) }); const _recordSchema = /*#__PURE__*/ v.object({ - $type: /*#__PURE__*/ v.optional( - /*#__PURE__*/ v.literal("app.blento.card.listRecords#record"), - ), - cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - collection: /*#__PURE__*/ v.nsidString(), - did: /*#__PURE__*/ v.didString(), - get record() { - return /*#__PURE__*/ v.optional(AppBlentoCard.mainSchema); - }, - rkey: /*#__PURE__*/ v.string(), - time_us: /*#__PURE__*/ v.integer(), - uri: /*#__PURE__*/ v.resourceUriString(), + $type: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.literal('app.blento.card.listRecords#record')), + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + collection: /*#__PURE__*/ v.nsidString(), + did: /*#__PURE__*/ v.didString(), + get record() { + return /*#__PURE__*/ v.optional(AppBlentoCard.mainSchema); + }, + rkey: /*#__PURE__*/ v.string(), + time_us: /*#__PURE__*/ v.integer(), + uri: /*#__PURE__*/ v.resourceUriString() }); type main$schematype = typeof _mainSchema; @@ -202,11 +194,11 @@ export const recordSchema = _recordSchema as recordSchema; export interface ProfileEntry extends v.InferInput {} export interface Record extends v.InferInput {} -export interface $params extends v.InferInput {} -export interface $output extends v.InferXRPCBodyInput {} +export interface $params extends v.InferInput {} +export interface $output extends v.InferXRPCBodyInput {} -declare module "@atcute/lexicons/ambient" { - interface XRPCQueries { - "app.blento.card.listRecords": mainSchema; - } +declare module '@atcute/lexicons/ambient' { + interface XRPCQueries { + 'app.blento.card.listRecords': mainSchema; + } } diff --git a/src/lexicon-types/types/app/blento/getCursor.ts b/src/lexicon-types/types/app/blento/getCursor.ts index ce2b38d..52016a6 100644 --- a/src/lexicon-types/types/app/blento/getCursor.ts +++ b/src/lexicon-types/types/app/blento/getCursor.ts @@ -1,17 +1,17 @@ -import type {} from "@atcute/lexicons"; -import * as v from "@atcute/lexicons/validations"; -import type {} from "@atcute/lexicons/ambient"; +import type {} from '@atcute/lexicons'; +import * as v from '@atcute/lexicons/validations'; +import type {} from '@atcute/lexicons/ambient'; -const _mainSchema = /*#__PURE__*/ v.query("app.blento.getCursor", { - params: null, - output: { - type: "lex", - schema: /*#__PURE__*/ v.object({ - date: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - seconds_ago: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), - time_us: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), - }), - }, +const _mainSchema = /*#__PURE__*/ v.query('app.blento.getCursor', { + params: null, + output: { + type: 'lex', + schema: /*#__PURE__*/ v.object({ + date: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + seconds_ago: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), + time_us: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()) + }) + } }); type main$schematype = typeof _mainSchema; @@ -21,10 +21,10 @@ export interface mainSchema extends main$schematype {} export const mainSchema = _mainSchema as mainSchema; export interface $params {} -export interface $output extends v.InferXRPCBodyInput {} +export interface $output extends v.InferXRPCBodyInput {} -declare module "@atcute/lexicons/ambient" { - interface XRPCQueries { - "app.blento.getCursor": mainSchema; - } +declare module '@atcute/lexicons/ambient' { + interface XRPCQueries { + 'app.blento.getCursor': mainSchema; + } } diff --git a/src/lexicon-types/types/app/blento/getOverview.ts b/src/lexicon-types/types/app/blento/getOverview.ts index 11b1bc1..170d737 100644 --- a/src/lexicon-types/types/app/blento/getOverview.ts +++ b/src/lexicon-types/types/app/blento/getOverview.ts @@ -1,26 +1,26 @@ -import type {} from "@atcute/lexicons"; -import * as v from "@atcute/lexicons/validations"; -import type {} from "@atcute/lexicons/ambient"; +import type {} from '@atcute/lexicons'; +import * as v from '@atcute/lexicons/validations'; +import type {} from '@atcute/lexicons/ambient'; const _collectionStatsSchema = /*#__PURE__*/ v.object({ - $type: /*#__PURE__*/ v.optional( - /*#__PURE__*/ v.literal("app.blento.getOverview#collectionStats"), - ), - collection: /*#__PURE__*/ v.string(), - records: /*#__PURE__*/ v.integer(), - unique_users: /*#__PURE__*/ v.integer(), + $type: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.literal('app.blento.getOverview#collectionStats') + ), + collection: /*#__PURE__*/ v.string(), + records: /*#__PURE__*/ v.integer(), + unique_users: /*#__PURE__*/ v.integer() }); -const _mainSchema = /*#__PURE__*/ v.query("app.blento.getOverview", { - params: null, - output: { - type: "lex", - schema: /*#__PURE__*/ v.object({ - get collections() { - return /*#__PURE__*/ v.array(collectionStatsSchema); - }, - total_records: /*#__PURE__*/ v.integer(), - }), - }, +const _mainSchema = /*#__PURE__*/ v.query('app.blento.getOverview', { + params: null, + output: { + type: 'lex', + schema: /*#__PURE__*/ v.object({ + get collections() { + return /*#__PURE__*/ v.array(collectionStatsSchema); + }, + total_records: /*#__PURE__*/ v.integer() + }) + } }); type collectionStats$schematype = typeof _collectionStatsSchema; @@ -29,19 +29,16 @@ type main$schematype = typeof _mainSchema; export interface collectionStatsSchema extends collectionStats$schematype {} export interface mainSchema extends main$schematype {} -export const collectionStatsSchema = - _collectionStatsSchema as collectionStatsSchema; +export const collectionStatsSchema = _collectionStatsSchema as collectionStatsSchema; export const mainSchema = _mainSchema as mainSchema; -export interface CollectionStats extends v.InferInput< - typeof collectionStatsSchema -> {} +export interface CollectionStats extends v.InferInput {} export interface $params {} -export interface $output extends v.InferXRPCBodyInput {} +export interface $output extends v.InferXRPCBodyInput {} -declare module "@atcute/lexicons/ambient" { - interface XRPCQueries { - "app.blento.getOverview": mainSchema; - } +declare module '@atcute/lexicons/ambient' { + interface XRPCQueries { + 'app.blento.getOverview': mainSchema; + } } diff --git a/src/lexicon-types/types/app/blento/getProfile.ts b/src/lexicon-types/types/app/blento/getProfile.ts index 58e78aa..cded05a 100644 --- a/src/lexicon-types/types/app/blento/getProfile.ts +++ b/src/lexicon-types/types/app/blento/getProfile.ts @@ -1,34 +1,32 @@ -import type {} from "@atcute/lexicons"; -import * as v from "@atcute/lexicons/validations"; -import type {} from "@atcute/lexicons/ambient"; +import type {} from '@atcute/lexicons'; +import * as v from '@atcute/lexicons/validations'; +import type {} from '@atcute/lexicons/ambient'; -const _mainSchema = /*#__PURE__*/ v.query("app.blento.getProfile", { - params: /*#__PURE__*/ v.object({ - /** - * DID or handle of the user - */ - actor: /*#__PURE__*/ v.actorIdentifierString(), - }), - output: { - type: "lex", - schema: /*#__PURE__*/ v.object({ - get profiles() { - return /*#__PURE__*/ v.array(profileEntrySchema); - }, - }), - }, +const _mainSchema = /*#__PURE__*/ v.query('app.blento.getProfile', { + params: /*#__PURE__*/ v.object({ + /** + * DID or handle of the user + */ + actor: /*#__PURE__*/ v.actorIdentifierString() + }), + output: { + type: 'lex', + schema: /*#__PURE__*/ v.object({ + get profiles() { + return /*#__PURE__*/ v.array(profileEntrySchema); + } + }) + } }); const _profileEntrySchema = /*#__PURE__*/ v.object({ - $type: /*#__PURE__*/ v.optional( - /*#__PURE__*/ v.literal("app.blento.getProfile#profileEntry"), - ), - cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - collection: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.nsidString()), - did: /*#__PURE__*/ v.didString(), - handle: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - record: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.unknown()), - rkey: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), + $type: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.literal('app.blento.getProfile#profileEntry')), + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + collection: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.nsidString()), + did: /*#__PURE__*/ v.didString(), + handle: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + record: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.unknown()), + rkey: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()) }); type main$schematype = typeof _mainSchema; @@ -42,11 +40,11 @@ export const profileEntrySchema = _profileEntrySchema as profileEntrySchema; export interface ProfileEntry extends v.InferInput {} -export interface $params extends v.InferInput {} -export interface $output extends v.InferXRPCBodyInput {} +export interface $params extends v.InferInput {} +export interface $output extends v.InferXRPCBodyInput {} -declare module "@atcute/lexicons/ambient" { - interface XRPCQueries { - "app.blento.getProfile": mainSchema; - } +declare module '@atcute/lexicons/ambient' { + interface XRPCQueries { + 'app.blento.getProfile': mainSchema; + } } diff --git a/src/lexicon-types/types/app/blento/notifyOfUpdate.ts b/src/lexicon-types/types/app/blento/notifyOfUpdate.ts index 488901a..15ee18b 100644 --- a/src/lexicon-types/types/app/blento/notifyOfUpdate.ts +++ b/src/lexicon-types/types/app/blento/notifyOfUpdate.ts @@ -1,47 +1,44 @@ -import type {} from "@atcute/lexicons"; -import * as v from "@atcute/lexicons/validations"; -import type {} from "@atcute/lexicons/ambient"; +import type {} from '@atcute/lexicons'; +import * as v from '@atcute/lexicons/validations'; +import type {} from '@atcute/lexicons/ambient'; -const _mainSchema = /*#__PURE__*/ v.procedure("app.blento.notifyOfUpdate", { - params: null, - input: { - type: "lex", - schema: /*#__PURE__*/ v.object({ - /** - * Single AT URI to fetch and index - */ - uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), - /** - * Batch of AT URIs to fetch and index (max 25) - * @maxLength 25 - */ - uris: /*#__PURE__*/ v.optional( - /*#__PURE__*/ v.constrain( - /*#__PURE__*/ v.array(/*#__PURE__*/ v.resourceUriString()), - [/*#__PURE__*/ v.arrayLength(0, 25)], - ), - ), - }), - }, - output: { - type: "lex", - schema: /*#__PURE__*/ v.object({ - /** - * Number of records deleted (not found on PDS) - */ - deleted: /*#__PURE__*/ v.integer(), - /** - * Errors for individual URIs that could not be processed - */ - errors: /*#__PURE__*/ v.optional( - /*#__PURE__*/ v.array(/*#__PURE__*/ v.string()), - ), - /** - * Number of records created or updated - */ - indexed: /*#__PURE__*/ v.integer(), - }), - }, +const _mainSchema = /*#__PURE__*/ v.procedure('app.blento.notifyOfUpdate', { + params: null, + input: { + type: 'lex', + schema: /*#__PURE__*/ v.object({ + /** + * Single AT URI to fetch and index + */ + uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), + /** + * Batch of AT URIs to fetch and index (max 25) + * @maxLength 25 + */ + uris: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.array(/*#__PURE__*/ v.resourceUriString()), [ + /*#__PURE__*/ v.arrayLength(0, 25) + ]) + ) + }) + }, + output: { + type: 'lex', + schema: /*#__PURE__*/ v.object({ + /** + * Number of records deleted (not found on PDS) + */ + deleted: /*#__PURE__*/ v.integer(), + /** + * Errors for individual URIs that could not be processed + */ + errors: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.array(/*#__PURE__*/ v.string())), + /** + * Number of records created or updated + */ + indexed: /*#__PURE__*/ v.integer() + }) + } }); type main$schematype = typeof _mainSchema; @@ -51,11 +48,11 @@ export interface mainSchema extends main$schematype {} export const mainSchema = _mainSchema as mainSchema; export interface $params {} -export interface $input extends v.InferXRPCBodyInput {} -export interface $output extends v.InferXRPCBodyInput {} +export interface $input extends v.InferXRPCBodyInput {} +export interface $output extends v.InferXRPCBodyInput {} -declare module "@atcute/lexicons/ambient" { - interface XRPCProcedures { - "app.blento.notifyOfUpdate": mainSchema; - } +declare module '@atcute/lexicons/ambient' { + interface XRPCProcedures { + 'app.blento.notifyOfUpdate': mainSchema; + } } diff --git a/src/lexicon-types/types/app/blento/page.ts b/src/lexicon-types/types/app/blento/page.ts index 2fff7db..70f78e0 100644 --- a/src/lexicon-types/types/app/blento/page.ts +++ b/src/lexicon-types/types/app/blento/page.ts @@ -1,33 +1,31 @@ -import type {} from "@atcute/lexicons"; -import * as v from "@atcute/lexicons/validations"; -import type {} from "@atcute/lexicons/ambient"; +import type {} from '@atcute/lexicons'; +import * as v from '@atcute/lexicons/validations'; +import type {} from '@atcute/lexicons/ambient'; const _mainSchema = /*#__PURE__*/ v.record( - /*#__PURE__*/ v.string(), - /*#__PURE__*/ v.object({ - $type: /*#__PURE__*/ v.literal("app.blento.page"), - description: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * @accept image/* - */ - icon: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.blob()), - name: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - get preferences() { - return /*#__PURE__*/ v.optional(preferencesSchema); - }, - url: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.genericUriString()), - }), + /*#__PURE__*/ v.string(), + /*#__PURE__*/ v.object({ + $type: /*#__PURE__*/ v.literal('app.blento.page'), + description: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * @accept image/* + */ + icon: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.blob()), + name: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + get preferences() { + return /*#__PURE__*/ v.optional(preferencesSchema); + }, + url: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.genericUriString()) + }) ); const _preferencesSchema = /*#__PURE__*/ v.object({ - $type: /*#__PURE__*/ v.optional( - /*#__PURE__*/ v.literal("app.blento.page#preferences"), - ), - accentColor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - baseColor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - editedOn: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), - hideProfile: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), - hideProfileSection: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), - profilePosition: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + $type: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.literal('app.blento.page#preferences')), + accentColor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + baseColor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + editedOn: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.integer()), + hideProfile: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), + hideProfileSection: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), + profilePosition: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()) }); type main$schematype = typeof _mainSchema; @@ -42,8 +40,8 @@ export const preferencesSchema = _preferencesSchema as preferencesSchema; export interface Main extends v.InferInput {} export interface Preferences extends v.InferInput {} -declare module "@atcute/lexicons/ambient" { - interface Records { - "app.blento.page": mainSchema; - } +declare module '@atcute/lexicons/ambient' { + interface Records { + 'app.blento.page': mainSchema; + } } diff --git a/src/lexicon-types/types/app/blento/page/getRecord.ts b/src/lexicon-types/types/app/blento/page/getRecord.ts index 95f5fec..4f23ec9 100644 --- a/src/lexicon-types/types/app/blento/page/getRecord.ts +++ b/src/lexicon-types/types/app/blento/page/getRecord.ts @@ -1,50 +1,48 @@ -import type {} from "@atcute/lexicons"; -import * as v from "@atcute/lexicons/validations"; -import type {} from "@atcute/lexicons/ambient"; -import * as AppBlentoPage from "../page.js"; +import type {} from '@atcute/lexicons'; +import * as v from '@atcute/lexicons/validations'; +import type {} from '@atcute/lexicons/ambient'; +import * as AppBlentoPage from '../page.js'; -const _mainSchema = /*#__PURE__*/ v.query("app.blento.page.getRecord", { - params: /*#__PURE__*/ v.object({ - /** - * Include profile + identity info keyed by DID - */ - profiles: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), - /** - * AT URI of the record - */ - uri: /*#__PURE__*/ v.resourceUriString(), - }), - output: { - type: "lex", - schema: /*#__PURE__*/ v.object({ - cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - collection: /*#__PURE__*/ v.nsidString(), - did: /*#__PURE__*/ v.didString(), - get profiles() { - return /*#__PURE__*/ v.optional( - /*#__PURE__*/ v.array(profileEntrySchema), - ); - }, - get record() { - return /*#__PURE__*/ v.optional(AppBlentoPage.mainSchema); - }, - rkey: /*#__PURE__*/ v.string(), - time_us: /*#__PURE__*/ v.integer(), - uri: /*#__PURE__*/ v.resourceUriString(), - }), - }, +const _mainSchema = /*#__PURE__*/ v.query('app.blento.page.getRecord', { + params: /*#__PURE__*/ v.object({ + /** + * Include profile + identity info keyed by DID + */ + profiles: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), + /** + * AT URI of the record + */ + uri: /*#__PURE__*/ v.resourceUriString() + }), + output: { + type: 'lex', + schema: /*#__PURE__*/ v.object({ + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + collection: /*#__PURE__*/ v.nsidString(), + did: /*#__PURE__*/ v.didString(), + get profiles() { + return /*#__PURE__*/ v.optional(/*#__PURE__*/ v.array(profileEntrySchema)); + }, + get record() { + return /*#__PURE__*/ v.optional(AppBlentoPage.mainSchema); + }, + rkey: /*#__PURE__*/ v.string(), + time_us: /*#__PURE__*/ v.integer(), + uri: /*#__PURE__*/ v.resourceUriString() + }) + } }); const _profileEntrySchema = /*#__PURE__*/ v.object({ - $type: /*#__PURE__*/ v.optional( - /*#__PURE__*/ v.literal("app.blento.page.getRecord#profileEntry"), - ), - cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - collection: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.nsidString()), - did: /*#__PURE__*/ v.didString(), - handle: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - record: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.unknown()), - rkey: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), + $type: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.literal('app.blento.page.getRecord#profileEntry') + ), + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + collection: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.nsidString()), + did: /*#__PURE__*/ v.didString(), + handle: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + record: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.unknown()), + rkey: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()) }); type main$schematype = typeof _mainSchema; @@ -58,11 +56,11 @@ export const profileEntrySchema = _profileEntrySchema as profileEntrySchema; export interface ProfileEntry extends v.InferInput {} -export interface $params extends v.InferInput {} -export interface $output extends v.InferXRPCBodyInput {} +export interface $params extends v.InferInput {} +export interface $output extends v.InferXRPCBodyInput {} -declare module "@atcute/lexicons/ambient" { - interface XRPCQueries { - "app.blento.page.getRecord": mainSchema; - } +declare module '@atcute/lexicons/ambient' { + interface XRPCQueries { + 'app.blento.page.getRecord': mainSchema; + } } diff --git a/src/lexicon-types/types/app/blento/page/listRecords.ts b/src/lexicon-types/types/app/blento/page/listRecords.ts index b21fdb5..643a284 100644 --- a/src/lexicon-types/types/app/blento/page/listRecords.ts +++ b/src/lexicon-types/types/app/blento/page/listRecords.ts @@ -1,91 +1,81 @@ -import type {} from "@atcute/lexicons"; -import * as v from "@atcute/lexicons/validations"; -import type {} from "@atcute/lexicons/ambient"; -import * as AppBlentoPage from "../page.js"; +import type {} from '@atcute/lexicons'; +import * as v from '@atcute/lexicons/validations'; +import type {} from '@atcute/lexicons/ambient'; +import * as AppBlentoPage from '../page.js'; -const _mainSchema = /*#__PURE__*/ v.query("app.blento.page.listRecords", { - params: /*#__PURE__*/ v.object({ - /** - * Filter by DID or handle (triggers on-demand backfill) - */ - actor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.actorIdentifierString()), - cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * Filter by description - */ - description: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * @minimum 1 - * @maximum 200 - * @default 50 - */ - limit: /*#__PURE__*/ v.optional( - /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.integer(), [ - /*#__PURE__*/ v.integerRange(1, 200), - ]), - 50, - ), - /** - * Filter by name - */ - name: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - /** - * Sort direction (default: desc for dates/numbers/counts, asc for strings) - */ - order: /*#__PURE__*/ v.optional( - /*#__PURE__*/ v.string<"asc" | "desc" | (string & {})>(), - ), - /** - * Include profile + identity info keyed by DID - */ - profiles: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), - /** - * Field to sort by (default: time_us) - */ - sort: /*#__PURE__*/ v.optional( - /*#__PURE__*/ v.string<"description" | "name" | (string & {})>(), - ), - }), - output: { - type: "lex", - schema: /*#__PURE__*/ v.object({ - cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - get profiles() { - return /*#__PURE__*/ v.optional( - /*#__PURE__*/ v.array(profileEntrySchema), - ); - }, - get records() { - return /*#__PURE__*/ v.array(recordSchema); - }, - }), - }, +const _mainSchema = /*#__PURE__*/ v.query('app.blento.page.listRecords', { + params: /*#__PURE__*/ v.object({ + /** + * Filter by DID or handle (triggers on-demand backfill) + */ + actor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.actorIdentifierString()), + cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Filter by description + */ + description: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * @minimum 1 + * @maximum 200 + * @default 50 + */ + limit: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.constrain(/*#__PURE__*/ v.integer(), [/*#__PURE__*/ v.integerRange(1, 200)]), + 50 + ), + /** + * Filter by name + */ + name: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + /** + * Sort direction (default: desc for dates/numbers/counts, asc for strings) + */ + order: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string<'asc' | 'desc' | (string & {})>()), + /** + * Include profile + identity info keyed by DID + */ + profiles: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.boolean()), + /** + * Field to sort by (default: time_us) + */ + sort: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string<'description' | 'name' | (string & {})>()) + }), + output: { + type: 'lex', + schema: /*#__PURE__*/ v.object({ + cursor: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + get profiles() { + return /*#__PURE__*/ v.optional(/*#__PURE__*/ v.array(profileEntrySchema)); + }, + get records() { + return /*#__PURE__*/ v.array(recordSchema); + } + }) + } }); const _profileEntrySchema = /*#__PURE__*/ v.object({ - $type: /*#__PURE__*/ v.optional( - /*#__PURE__*/ v.literal("app.blento.page.listRecords#profileEntry"), - ), - cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - collection: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.nsidString()), - did: /*#__PURE__*/ v.didString(), - handle: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - record: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.unknown()), - rkey: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()), + $type: /*#__PURE__*/ v.optional( + /*#__PURE__*/ v.literal('app.blento.page.listRecords#profileEntry') + ), + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + collection: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.nsidString()), + did: /*#__PURE__*/ v.didString(), + handle: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + record: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.unknown()), + rkey: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + uri: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.resourceUriString()) }); const _recordSchema = /*#__PURE__*/ v.object({ - $type: /*#__PURE__*/ v.optional( - /*#__PURE__*/ v.literal("app.blento.page.listRecords#record"), - ), - cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), - collection: /*#__PURE__*/ v.nsidString(), - did: /*#__PURE__*/ v.didString(), - get record() { - return /*#__PURE__*/ v.optional(AppBlentoPage.mainSchema); - }, - rkey: /*#__PURE__*/ v.string(), - time_us: /*#__PURE__*/ v.integer(), - uri: /*#__PURE__*/ v.resourceUriString(), + $type: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.literal('app.blento.page.listRecords#record')), + cid: /*#__PURE__*/ v.optional(/*#__PURE__*/ v.string()), + collection: /*#__PURE__*/ v.nsidString(), + did: /*#__PURE__*/ v.didString(), + get record() { + return /*#__PURE__*/ v.optional(AppBlentoPage.mainSchema); + }, + rkey: /*#__PURE__*/ v.string(), + time_us: /*#__PURE__*/ v.integer(), + uri: /*#__PURE__*/ v.resourceUriString() }); type main$schematype = typeof _mainSchema; @@ -103,11 +93,11 @@ export const recordSchema = _recordSchema as recordSchema; export interface ProfileEntry extends v.InferInput {} export interface Record extends v.InferInput {} -export interface $params extends v.InferInput {} -export interface $output extends v.InferXRPCBodyInput {} +export interface $params extends v.InferInput {} +export interface $output extends v.InferXRPCBodyInput {} -declare module "@atcute/lexicons/ambient" { - interface XRPCQueries { - "app.blento.page.listRecords": mainSchema; - } +declare module '@atcute/lexicons/ambient' { + interface XRPCQueries { + 'app.blento.page.listRecords': mainSchema; + } } diff --git a/src/lib/website/EditableWebsite.svelte b/src/lib/website/EditableWebsite.svelte index 82e3463..08f22bf 100644 --- a/src/lib/website/EditableWebsite.svelte +++ b/src/lib/website/EditableWebsite.svelte @@ -263,7 +263,6 @@ saveSuccess = true; launchConfetti(); - } catch (error) { console.error(error); showSaveModal = false; diff --git a/src/lib/website/Profile.svelte b/src/lib/website/Profile.svelte index 292e9fc..8dd7054 100644 --- a/src/lib/website/Profile.svelte +++ b/src/lib/website/Profile.svelte @@ -24,7 +24,11 @@ const profileUrl = $derived.by(() => { if (page.data.customDomain) return `${page.url.origin}/`; const pubUrl = data.publication?.url; - if (pubUrl && /^https?:\/\//.test(pubUrl) && !/^https?:\/\/([^/]*\.)?blento\.app/i.test(pubUrl)) { + if ( + pubUrl && + /^https?:\/\//.test(pubUrl) && + !/^https?:\/\/([^/]*\.)?blento\.app/i.test(pubUrl) + ) { return pubUrl; } const handle = data.profile?.handle; -- 2.51.2 From 4076d32347b1baa488278684deb13368c8f60f44 Mon Sep 17 00:00:00 2001 From: Florian <45694132+flo-bit@users.noreply.github.com> Date: Thu, 16 Apr 2026 00:05:10 +0200 Subject: [PATCH 7/7] add nodejs compat --- wrangler.jsonc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrangler.jsonc b/wrangler.jsonc index d9d39bb..036969c 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -7,7 +7,7 @@ "name": "blento", "main": ".svelte-kit/cloudflare/_worker.js", "compatibility_date": "2025-12-25", - "compatibility_flags": ["nodejs_als"], + "compatibility_flags": ["nodejs_als", "nodejs_compat"], "assets": { "binding": "ASSETS", "directory": ".svelte-kit/cloudflare"