diff --git a/.gitignore b/.gitignore index 7fa9df7..a5a8019 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,3 @@ vite.config.ts.timestamp-* test-results .contrail/ -/src/lib/contrail-active/ diff --git a/README.md b/README.md index 7bfeb80..4fee3c7 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,20 @@ review movies and tv shows with your atmosphere account, using popfeed.social's ## API Worker -The public Contrail API is a separate Cloudflare Worker under [`api/`](api/README.md). The web app has explicit production and local Contrail modes: +The public Contrail API is a separate Cloudflare Worker under [`api/`](api/README.md). Contrail's generated client supports both the deployed target and the local API: ```sh pnpm dev # use the deployed API at https://api.atmo.watch -pnpm api:dev # start only the local API +pnpm api:dev # start only the local SQLite API pnpm dev:local # use an already-running local API pnpm dev:stack # start the local API and web app together ``` -Local mode waits for `http://127.0.0.1:8787` by default and generates an ignored consumer contract under `.contrail/local-consumer`. The API command creates the ignored `api/.dev.vars` from `api/.dev.vars.example` when needed. For a custom URL, set both `ATMO_LOCAL_API_URL` and `CONTRAIL_PUBLIC_ENDPOINT` in that vars file. Local mode never falls back to production when the API is unavailable. +Local mode uses `http://127.0.0.1:8787` and stores its SQLite database under `.contrail/`. Set `CONTRAIL_URL` to another loopback URL when running the web app against a custom local port. It never falls back to production when the local API is unavailable. -The tracked generated contract under `src/lib/contrail` represents the API source tree. `src/lib/contrail-targets/prod.ts` pins the currently deployed production runtime contract. After deploying an API contract change, run `pnpm contrail:update:prod` and commit the regenerated production target and consumer files before deploying the web app. +Run `pnpm contrail:generate` after changing the API config to regenerate the typed source contract. After deploying that contract with `pnpm api:deploy`, run `pnpm contrail:update:prod` to refresh the production provider lock and generated target. Commit the regenerated lock, client, Lexicons, and types. -Use `pnpm api:check` and `pnpm api:deploy` for API validation and deployment. +Use `pnpm api:check` for API validation. ## Cloudflare deployment diff --git a/api/.dev.vars.example b/api/.dev.vars.example deleted file mode 100644 index 531f9e0..0000000 --- a/api/.dev.vars.example +++ /dev/null @@ -1 +0,0 @@ -CONTRAIL_PUBLIC_ENDPOINT="http://127.0.0.1:8787" diff --git a/api/.gitignore b/api/.gitignore index ba1a2cd..b047554 100644 --- a/api/.gitignore +++ b/api/.gitignore @@ -2,4 +2,3 @@ node_modules/ .wrangler/ .dev.vars .dev.vars.* -!.dev.vars.example diff --git a/api/README.md b/api/README.md index 3c12efd..63e0dd5 100644 --- a/api/README.md +++ b/api/README.md @@ -37,7 +37,7 @@ pnpm api:dev pnpm --dir api backfill:dev ``` -Wrangler stores the local D1 database under `api/.wrangler/`. The dev command creates an ignored `api/.dev.vars` with the loopback public-service endpoint so local consumers receive a contract for `http://127.0.0.1:8787`; production continues to advertise `https://api.atmo.watch` from `wrangler.jsonc`. +Contrail's local development server stores its SQLite database under the repository's ignored `.contrail/` directory and serves the source contract at `http://127.0.0.1:8787`. It does not alter the production provider lock. ## Updating the contract @@ -46,14 +46,13 @@ pnpm --dir api lexicons:all pnpm api:check ``` -After the production service is live, connect the web app to it from the repository root: +Regenerate the web app's source API from the repository root before deployment: ```bash -rm -rf src/lib/contrail contrail.lock.json -pnpm exec contrail connect https://api.atmo.watch \ - --out src/lib/contrail/lexicons \ - --client src/lib/contrail/index.ts \ - --client-types src/lib/contrail/types/index.ts +pnpm contrail:generate +pnpm api:check +pnpm api:deploy +pnpm contrail:update:prod ``` -Review and commit the regenerated client, lock, Lexicons, and types. +The source connection updates types and local client metadata without changing the production lock. The final command reconnects to the deployed service and updates that lock. Review and commit the regenerated client, lock, Lexicons, and types. diff --git a/api/package.json b/api/package.json index e58587e..3fe05d7 100644 --- a/api/package.json +++ b/api/package.json @@ -4,9 +4,10 @@ "private": true, "type": "module", "scripts": { - "dev": "node ../scripts/ensure-api-dev-vars.mjs && contrail dev --wrangler", + "dev": "contrail dev --sqlite ../.contrail/dev.sqlite", + "dev:local": "contrail dev --sqlite ../.contrail/dev.sqlite", "deploy": "wrangler deploy", - "backfill:dev": "contrail backfill", + "backfill:dev": "contrail backfill --sqlite ../.contrail/dev.sqlite", "backfill:remote": "contrail backfill --remote", "lexicons": "contrail lexicons generate --public", "lexicons:all": "contrail lexicons all --public", @@ -14,7 +15,7 @@ "check": "pnpm run lexicons:check && tsc --noEmit" }, "dependencies": { - "@atmo-dev/contrail": "^0.17.0" + "@atmo-dev/contrail": "^0.19.0" }, "devDependencies": { "@atcute/lex-cli": "^3.2.1", diff --git a/api/src/worker.ts b/api/src/worker.ts index d593494..19521bb 100644 --- a/api/src/worker.ts +++ b/api/src/worker.ts @@ -2,30 +2,9 @@ import { createWorker } from '@atmo-dev/contrail/worker'; import { lexicons } from '../lexicons/generated'; import { config } from './contrail.config'; -const productionEndpoint = 'https://api.atmo.watch'; -const workers = new Map>(); -type WorkerEnv = Record & { CONTRAIL_PUBLIC_ENDPOINT?: string }; - -function getWorker(endpoint: string) { - let worker = workers.get(endpoint); - if (!worker) { - worker = createWorker(config, { - lexicons, - publicService: { - endpoint, - allowInsecureHttp: endpoint.startsWith('http://') - } - }); - workers.set(endpoint, worker); - } - return worker; -} - -export default { - fetch(request: Request, env: WorkerEnv) { - return getWorker(env.CONTRAIL_PUBLIC_ENDPOINT ?? productionEndpoint).fetch(request, env); - }, - scheduled(event: ScheduledEvent, env: WorkerEnv, ctx: ExecutionContext) { - return getWorker(env.CONTRAIL_PUBLIC_ENDPOINT ?? productionEndpoint).scheduled(event, env, ctx); +export default createWorker(config, { + lexicons, + publicService: { + endpoint: 'https://api.atmo.watch' } -}; +}); diff --git a/api/wrangler.jsonc b/api/wrangler.jsonc index 1592d81..2a99210 100644 --- a/api/wrangler.jsonc +++ b/api/wrangler.jsonc @@ -6,9 +6,6 @@ "observability": { "enabled": true }, - "vars": { - "CONTRAIL_PUBLIC_ENDPOINT": "https://api.atmo.watch" - }, "routes": [ { "pattern": "api.atmo.watch", diff --git a/contrail.lock.json b/contrail.lock.json index ef2b3a1..8379742 100644 --- a/contrail.lock.json +++ b/contrail.lock.json @@ -1,9 +1,8 @@ { "format": "contrail.provider-lock", - "version": 1, + "version": 2, "endpoint": "https://api.atmo.watch", "namespace": "watch.atmo", - "contractDigest": "sha256:edfc8edad45272bc6a66e56d2ea4bc61d1d7f3724304fdd492f9486c33d4032c", "lexiconDigest": "sha256:b81b7755af0e9755979eb3846efa10b1e7032c40d5682590a7aec7de0a651bb1", "methods": [ "watch.atmo.comment.getRecord", @@ -31,7 +30,9 @@ ], "serviceAuth": { "type": "atproto-service-auth", + "serviceDid": "did:web:api.atmo.watch", "audience": "did:web:api.atmo.watch#contrail", + "scope": "rpc?aud=did:web:api.atmo.watch%23contrail&lxm=watch.atmo.notifyOfUpdate", "methods": [ { "id": "watch.atmo.notifyOfUpdate", diff --git a/lex.config.js b/lex.config.js index 53ceb6f..645b450 100644 --- a/lex.config.js +++ b/lex.config.js @@ -2,8 +2,12 @@ export default { contrail: { endpoint: "https://api.atmo.watch", - serviceDid: "did:web:api.atmo.watch#contrail", - scope: "rpc?lxm=watch.atmo.notifyOfUpdate&aud=did:web:api.atmo.watch%23contrail", + serviceDid: "did:web:api.atmo.watch", + serviceAudience: "did:web:api.atmo.watch#contrail", + scope: "rpc?aud=did:web:api.atmo.watch%23contrail&lxm=watch.atmo.notifyOfUpdate", + protectedMethods: [ + "watch.atmo.notifyOfUpdate", + ], collections: [ "app.bsky.actor.profile", "social.popfeed.actor.profile", @@ -15,7 +19,7 @@ export default { ], }, generate: { - files: ["src/lib/contrail/lexicons/**/*.json"], + files: ["src/lib/contrail/lexicons/source/**/*.json"], outdir: "src/lib/contrail/types/", }, }; diff --git a/package.json b/package.json index 530846e..927c9d9 100644 --- a/package.json +++ b/package.json @@ -8,23 +8,21 @@ "atproto:setup": "atproto-oauth setup", "atproto:keygen": "atproto-oauth keygen", "atproto:secret": "atproto-oauth secret", - "contrail:use:prod": "node scripts/select-contrail.mjs prod", - "contrail:use:local": "node scripts/select-contrail.mjs local", - "contrail:update:prod": "node scripts/update-prod-contrail.mjs", - "dev": "node scripts/select-contrail.mjs prod && vite dev", - "dev:prod": "node scripts/select-contrail.mjs prod && vite dev", - "dev:local": "node scripts/select-contrail.mjs local && vite dev", - "dev:stack": "node scripts/dev-stack.mjs", - "build": "node scripts/select-contrail.mjs prod && vite build", + "contrail:generate": "contrail connect api --out src/lib/contrail/lexicons --client src/lib/contrail/index.ts --client-types src/lib/contrail/types/index.ts", + "contrail:update:prod": "contrail connect https://api.atmo.watch --out src/lib/contrail/lexicons --client src/lib/contrail/index.ts --client-types src/lib/contrail/types/index.ts --update", + "dev": "vite dev", + "dev:prod": "vite dev", + "dev:local": "CONTRAIL_URL=http://127.0.0.1:8787 vite dev", + "dev:stack": "pnpm --parallel --filter . --filter @atmo-watch/api run dev:local", + "build": "vite build", "preview": "vite preview", - "deploy": "node scripts/select-contrail.mjs prod && vite build && wrangler deploy", - "api:dev": "node scripts/ensure-api-dev-vars.mjs && cd api && contrail dev --wrangler", - "api:check": "cd api && contrail lexicons check --public && tsc --noEmit", - "api:deploy": "cd api && wrangler deploy", - "prepare": "node scripts/select-contrail.mjs prod && (svelte-kit sync || echo '')", - "check": "node scripts/select-contrail.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "check:local": "node scripts/select-contrail.mjs local && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", - "check:watch": "node scripts/select-contrail.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", + "deploy": "pnpm run build && wrangler deploy", + "api:dev": "pnpm --dir api dev", + "api:check": "pnpm --dir api check", + "api:deploy": "pnpm --dir api deploy", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "lint": "prettier --check . && eslint .", "format": "prettier --write .", "test:unit": "vitest", @@ -64,7 +62,7 @@ "@atcute/atproto": "^4.0.0", "@atcute/client": "^5.1.1", "@atcute/lexicons": "^2.0.3", - "@atmo-dev/contrail": "^0.17.0", + "@atmo-dev/contrail": "^0.19.0", "@ethercorps/sveltekit-og": "^4.3.0", "@foxui/core": "^0.9.1", "@foxui/social": "^0.8.10", diff --git a/patches/@atmo-dev__contrail@0.17.0.patch b/patches/@atmo-dev__contrail@0.17.0.patch deleted file mode 100644 index 8d02377..0000000 --- a/patches/@atmo-dev__contrail@0.17.0.patch +++ /dev/null @@ -1,220 +0,0 @@ -diff --git a/dist/chunk-C5MWLAVY.js b/dist/chunk-C5MWLAVY.js -index f8b1fc4bdf6a4f40f6d183560dc3437523af095a..75b4580566a3b9b1498a185738e3bbec7566024c 100644 ---- a/dist/chunk-C5MWLAVY.js -+++ b/dist/chunk-C5MWLAVY.js -@@ -5717,16 +5717,17 @@ function createApp(db, config, options = {}) { - c.header("etag", `"${manifest.contract.digest}"`); - return c.json(manifest); - }); -- if (serviceAuth && serviceAuth.audience === `did:web:${new URL(options.publicService.endpoint).hostname}`) { -+ const serviceDid = serviceAuth?.audience.split("#", 1)[0]; -+ if (serviceAuth && serviceDid === `did:web:${new URL(options.publicService.endpoint).hostname}`) { - app.get("/.well-known/did.json", (c) => { - c.header("content-type", "application/did+ld+json; charset=UTF-8"); - c.header("cache-control", "public, max-age=300"); - return c.json({ - "@context": ["https://www.w3.org/ns/did/v1"], -- id: serviceAuth.audience, -+ id: serviceDid, - service: [ - { -- id: `${serviceAuth.audience}#contrail`, -+ id: serviceAuth.audience, - type: "ContrailService", - serviceEndpoint: options.publicService.endpoint - } -diff --git a/dist/chunk-FCTUTPWI.js b/dist/chunk-FCTUTPWI.js -index ff439ec205a76d85d93e1524b33d07d8aa1c9562..0318e60da81eda9fd10ca9ecbc1d9b760c904986 100644 ---- a/dist/chunk-FCTUTPWI.js -+++ b/dist/chunk-FCTUTPWI.js -@@ -1,5 +1,10 @@ - // src/core/types.ts - import { isDid } from "@atcute/lexicons/syntax"; -+function isServiceAudience(value) { -+ if (typeof value !== "string") return false; -+ const fragmentIndex = value.indexOf("#"); -+ return fragmentIndex > 0 && fragmentIndex < value.length - 1 && value.indexOf("#", fragmentIndex + 1) === -1 && isDid(value.slice(0, fragmentIndex)); -+} - var DEFAULT_FEED_MAX_ITEMS = 200; - var DEFAULT_FOLLOW_NSID = "app.bsky.graph.follow"; - var DEFAULT_FOLLOW_SHORT = "follow"; -@@ -72,8 +77,8 @@ function resolveConfig(config) { - throw new TypeError("orderedSource requires non-empty source and epoch values"); - } - if (config.serviceAuth) { -- if (!isDid(config.serviceAuth.audience)) { -- throw new TypeError("serviceAuth.audience must be a plain DID"); -+ if (!isServiceAudience(config.serviceAuth.audience)) { -+ throw new TypeError("serviceAuth.audience must be an absolute DID service reference"); - } - if (!Array.isArray(config.serviceAuth.methods) || new Set(config.serviceAuth.methods).size !== config.serviceAuth.methods.length) { - throw new TypeError("serviceAuth.methods must contain unique methods"); -diff --git a/dist/chunk-RWCV6ET2.js b/dist/chunk-RWCV6ET2.js -index 9dedd47bf752e9890333088517416b05b2daa4fa..f050adaaa8305b9c03771fbb0d4339d93b4b52f6 100644 ---- a/dist/chunk-RWCV6ET2.js -+++ b/dist/chunk-RWCV6ET2.js -@@ -6,6 +6,11 @@ import { - - // src/public-service.ts - import { isDid, isNsid } from "@atcute/lexicons/syntax"; -+function isServiceAudience(value) { -+ if (typeof value !== "string") return false; -+ const fragmentIndex = value.indexOf("#"); -+ return fragmentIndex > 0 && fragmentIndex < value.length - 1 && value.indexOf("#", fragmentIndex + 1) === -1 && isDid(value.slice(0, fragmentIndex)); -+} - function isLoopbackHostname(hostname) { - return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]"; - } -@@ -271,7 +276,7 @@ function isPublicServiceAuthContract(value) { - if (value === null || value === void 0) return true; - if (!value || typeof value !== "object") return false; - const auth = value; -- return auth.type === "atproto-service-auth" && typeof auth.audience === "string" && isDid(auth.audience) && Array.isArray(auth.methods) && auth.methods.every( -+ return auth.type === "atproto-service-auth" && isServiceAudience(auth.audience) && Array.isArray(auth.methods) && auth.methods.every( - (method) => !!method && typeof method === "object" && typeof method.id === "string" && isNsid(method.id) && (method.type === "query" || method.type === "procedure") - ); - } -diff --git a/dist/cli.js b/dist/cli.js -index f3bc40ddf1dcc505da08f9d6646e8a3a1dcdf387..2ca7653e57a2e8ba41823e2db787e9bc4dc29d74 100755 ---- a/dist/cli.js -+++ b/dist/cli.js -@@ -383,6 +383,12 @@ function formatStringArray(values, indentation) { - ${items} - ${" ".repeat(indentation)}]`; - } -+function generatedServiceAuthScope(serviceAuth) { -+ if (!serviceAuth || serviceAuth.methods.length === 0) return null; -+ const methods = serviceAuth.methods.map(({ id }) => `lxm=${encodeURIComponent(id)}`).join("&"); -+ const audience = serviceAuth.audience.replace("#", "%23"); -+ return `rpc?${methods}&aud=${audience}`; -+} - var GENERATED_LEXICON_CONFIG_HEADER = "// Generated by `contrail connect`. Re-run the command to update; do not edit.\n"; - async function ensureConsumerLexiconConfig(options) { - const root = resolve(options.root); -@@ -402,7 +408,7 @@ async function ensureConsumerLexiconConfig(options) { - ); - const typesRoot = relative(root, dirname2(typesIndex)).replaceAll("\\", "/"); - const serviceDid = options.lock.serviceAuth?.audience ?? null; -- const scope = serviceDid ? `rpc?lxm=*&aud=${serviceDid}` : null; -+ const scope = generatedServiceAuthScope(options.lock.serviceAuth); - const source = `${GENERATED_LEXICON_CONFIG_HEADER}export default { - contrail: { - endpoint: ${JSON.stringify(options.lock.endpoint)}, -@@ -473,7 +479,7 @@ async function ensureConsumerClientModule(options) { - `; - } - const serviceDid = options.lock.serviceAuth?.audience; -- const scope = serviceDid ? `rpc?lxm=*&aud=${serviceDid}` : null; -+ const scope = generatedServiceAuthScope(options.lock.serviceAuth); - const protectedMethods = options.lock.serviceAuth?.methods.map(({ id }) => id) ?? []; - const serviceMethods = [ - .../* @__PURE__ */ new Set([...options.lock.methods, ...protectedMethods]) -diff --git a/dist/public-client.d.ts b/dist/public-client.d.ts -index c2f215fdde9a70b2f0b97757c8282260ef15571a..097a591bad439e29c1e19edb69c89c2291c1e0ce 100644 ---- a/dist/public-client.d.ts -+++ b/dist/public-client.d.ts -@@ -1,5 +1,5 @@ - import { Client, FetchHandler } from '@atcute/client'; --import { Nsid, Did } from '@atcute/lexicons/syntax'; -+import { Nsid, AtprotoAudience } from '@atcute/lexicons/syntax'; - - interface PublicServiceClientOptions { - /** Canonical public Contrail HTTPS origin. */ -@@ -11,9 +11,9 @@ interface PublicServiceClientOptions { - contractDigest?: string; - /** Optional receiving-service DID from `lex.config.js`. Supplying it also - * makes the required OAuth permission available as `client.scope`. */ -- serviceDid?: Did; -+ serviceDid?: AtprotoAudience; - /** Optional precomputed OAuth permission. Must match `serviceDid`. */ -- scope?: `rpc?lxm=*&aud=${string}`; -+ scope?: `rpc?${string}`; - /** Exact XRPC methods served by this provider. Supplying the verified list - * lets authenticated clients route all other methods to the user's PDS. */ - serviceMethods?: readonly Nsid[]; -@@ -37,7 +37,7 @@ type PublicServiceClient = Client & { - /** Canonical public Contrail origin. */ - readonly endpoint: string; - /** OAuth permission required by protected methods, or null when unconfigured. */ -- readonly scope: `rpc?lxm=*&aud=${string}` | null; -+ readonly scope: `rpc?${string}` | null; - /** Record collections whose successful writes trigger notification. */ - readonly collections: readonly Nsid[]; - /** Combine this provider with an authenticated PDS client. Provider methods -@@ -45,7 +45,7 @@ type PublicServiceClient = Client & { - * record writes notify Contrail before returning their original response. */ - authenticated(authenticatedClient: Client, options?: PublicServiceAuthenticatedOptions): PublicServiceClient; - }; --declare function publicServiceOAuthScope(audience: Did): `rpc?lxm=*&aud=${string}`; -+declare function publicServiceOAuthScope(audience: AtprotoAudience): `rpc?lxm=*&aud=${string}`; - /** Fetch handler that verifies an optional pinned contract once, then keeps - * unpinned anonymous reads cheap while automatically minting, caching, and - * attaching method-bound AT Protocol service tokens after a protected route -@@ -54,9 +54,9 @@ declare function publicServiceFetchHandler(options: PublicServiceClientOptions): - /** Create a typed Atcute client for anonymous and service-auth Contrail methods. - * Generated Lexicon imports still supply the method-specific TypeScript API. */ - declare function createPublicServiceClient(options: PublicServiceClientOptions & { -- serviceDid: Did; -+ serviceDid: AtprotoAudience; - }): PublicServiceClient & { -- readonly scope: `rpc?lxm=*&aud=${string}`; -+ readonly scope: `rpc?${string}`; - }; - declare function createPublicServiceClient(options: PublicServiceClientOptions): PublicServiceClient; - -diff --git a/dist/public-client.js b/dist/public-client.js -index 8c95c170d5946e52ebec9267a7d00a443c610ede..f0ded53c6fb0e5f483af73ea4f5369cbe91fe87b 100644 ---- a/dist/public-client.js -+++ b/dist/public-client.js -@@ -15,7 +15,12 @@ var TOKEN_EXPIRY_SKEW_MS = 5e3; - var PublicServiceContractError = class extends Error { - }; - function publicServiceOAuthScope(audience) { -- return `rpc?lxm=*&aud=${audience}`; -+ return `rpc?lxm=*&aud=${audience.replace("#", "%23")}`; -+} -+function scopeMatchesServiceAudience(scope, audience) { -+ if (!scope.startsWith("rpc?")) return false; -+ const params = new URLSearchParams(scope.slice(4)); -+ return params.getAll("aud").length === 1 && params.get("aud") === audience && params.getAll("lxm").length > 0; - } - function xrpcMethod(pathname) { - const path = pathname.startsWith("http") ? new URL(pathname).pathname : new URL(pathname, "https://contrail.invalid").pathname; -@@ -210,9 +215,9 @@ function createClient(options, authenticatedOptions = {}) { - handler: publicServiceFetchHandler({ ...options, endpoint }) - }); - const expectedScope = options.serviceDid ? publicServiceOAuthScope(options.serviceDid) : null; -- if (options.scope && options.scope !== expectedScope) { -+ if (options.scope && (!options.serviceDid || !scopeMatchesServiceAudience(options.scope, options.serviceDid))) { - throw new Error( -- `Contrail OAuth scope mismatch: expected ${expectedScope ?? "none"}, received ${options.scope}` -+ `Contrail OAuth scope audience mismatch: expected ${options.serviceDid ?? "none"}, received ${options.scope}` - ); - } - const scope = options.scope ?? expectedScope; -diff --git a/dist/types-BWmLk25T.d.ts b/dist/types-BWmLk25T.d.ts -index 17dbf762cee1c563435300cd1a9395acc3ec17a3..9404a89bc40c836e67cd81b7bd73ac032e960bc6 100644 ---- a/dist/types-BWmLk25T.d.ts -+++ b/dist/types-BWmLk25T.d.ts -@@ -252,7 +252,7 @@ interface OrderedSourceConfig { - } - type AtprotoServiceAuthMethod = "getFeed" | "notifyOfUpdate"; - interface AtprotoServiceAuthConfig { -- /** Plain service DID used as the exact JWT audience. */ -+ /** Absolute DID service reference used as the exact JWT audience. */ - audience: string; - /** Built-in methods that require a method-bound AT Protocol service token. */ - methods: AtprotoServiceAuthMethod[]; -diff --git a/dist/worker/index.js b/dist/worker/index.js -index a5f20d65d608bb3d7254242d1090f91d5054df52..3ed71769608206514842d0cb49e6bd8dd528de03 100644 ---- a/dist/worker/index.js -+++ b/dist/worker/index.js -@@ -15,7 +15,7 @@ import "../chunk-KN4SCF66.js"; - function createWorker(config, options = {}) { - const binding = options.binding ?? "DB"; - if (options.publicService) { -- normalizePublicServiceEndpoint(options.publicService.endpoint); -+ normalizePublicServiceEndpoint(options.publicService.endpoint, options.publicService); - validatePublicServiceLexicons(config, options.lexicons ?? []); - } - const contrail = new Contrail({ ...config, lexicons: options.lexicons }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 351bf9d..b635111 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,11 +4,6 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false -patchedDependencies: - '@atmo-dev/contrail@0.17.0': - hash: b565c0190062e0ffd64d16f502018a892b4f9d4461eb666f844bbc22eca56690 - path: patches/@atmo-dev__contrail@0.17.0.patch - importers: .: @@ -23,8 +18,8 @@ importers: specifier: ^2.0.3 version: 2.0.3 '@atmo-dev/contrail': - specifier: ^0.17.0 - version: 0.17.0(patch_hash=b565c0190062e0ffd64d16f502018a892b4f9d4461eb666f844bbc22eca56690)(@atcute/identity@2.0.2(@atcute/lexicons@2.0.3)(typescript@6.0.3))(prettier@3.9.6)(typescript@6.0.3)(wrangler@4.123.0(@cloudflare/workers-types@5.20260815.1)) + specifier: ^0.19.0 + version: 0.19.0(prettier@3.9.6)(typescript@6.0.3)(wrangler@4.123.0(@cloudflare/workers-types@5.20260815.1)) '@ethercorps/sveltekit-og': specifier: ^4.3.0 version: 4.3.0(@sveltejs/kit@2.70.2(@sveltejs/vite-plugin-svelte@7.3.0(svelte@5.56.9(@typescript-eslint/types@8.67.0))(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0)))(svelte@5.56.9(@typescript-eslint/types@8.67.0))(typescript@6.0.3)(vite@8.2.1(@types/node@24.13.3)(esbuild@0.28.1)(jiti@2.7.0))) @@ -138,8 +133,8 @@ importers: api: dependencies: '@atmo-dev/contrail': - specifier: ^0.17.0 - version: 0.17.0(patch_hash=b565c0190062e0ffd64d16f502018a892b4f9d4461eb666f844bbc22eca56690)(@atcute/identity@2.0.2(@atcute/lexicons@2.0.3)(typescript@6.0.3))(prettier@3.9.6)(typescript@6.0.3)(wrangler@4.123.0(@cloudflare/workers-types@5.20260815.1)) + specifier: ^0.19.0 + version: 0.19.0(prettier@3.9.6)(typescript@6.0.3)(wrangler@4.123.0(@cloudflare/workers-types@5.20260815.1)) devDependencies: '@atcute/lex-cli': specifier: ^3.2.1 @@ -305,8 +300,8 @@ packages: peerDependencies: '@atcute/lexicons': ^2.0.0 - '@atmo-dev/contrail@0.17.0': - resolution: {integrity: sha512-VtC+PExsGb7oUqcOje2gF54HbY4JHU6HU1xmewsl+5IslLam3hh80SleG/0kqQM+YApE8LQzNxypING8sqLmOA==} + '@atmo-dev/contrail@0.19.0': + resolution: {integrity: sha512-4eP4uHcX2r5f0/fQY+NvsiIHBz4BGwOlAWJ0GxMFW3Sr0sSVZm1Y1hC80S+BK8XL/Iv4JIC9RwbeawwZ7wg8IA==} hasBin: true peerDependencies: pg: ^8.0.0 @@ -3055,17 +3050,19 @@ snapshots: - '@atcute/cid' - typescript - '@atmo-dev/contrail@0.17.0(patch_hash=b565c0190062e0ffd64d16f502018a892b4f9d4461eb666f844bbc22eca56690)(@atcute/identity@2.0.2(@atcute/lexicons@2.0.3)(typescript@6.0.3))(prettier@3.9.6)(typescript@6.0.3)(wrangler@4.123.0(@cloudflare/workers-types@5.20260815.1))': + '@atmo-dev/contrail@0.19.0(prettier@3.9.6)(typescript@6.0.3)(wrangler@4.123.0(@cloudflare/workers-types@5.20260815.1))': dependencies: '@atcute/atproto': 4.0.4(@atcute/lexicons@2.0.3) '@atcute/cbor': 2.3.6(@atcute/cid@2.4.2) '@atcute/cid': 2.4.2 '@atcute/client': 5.1.1(@atcute/lexicons@2.0.3)(typescript@6.0.3) + '@atcute/identity': 2.0.2(@atcute/lexicons@2.0.3)(typescript@6.0.3) '@atcute/identity-resolver': 2.0.1(@atcute/identity@2.0.2(@atcute/lexicons@2.0.3)(typescript@6.0.3))(@atcute/lexicons@2.0.3)(typescript@6.0.3) '@atcute/jetstream': 2.0.2(@atcute/lexicons@2.0.3)(typescript@6.0.3) '@atcute/lex-cli': 3.2.1(@atcute/cbor@2.3.6(@atcute/cid@2.4.2))(@atcute/cid@2.4.2)(prettier@3.9.6)(typescript@6.0.3) '@atcute/lexicon-doc': 3.0.2(@atcute/lexicons@2.0.3)(typescript@6.0.3) '@atcute/lexicons': 2.0.3 + '@atcute/oauth-types': 1.0.1(typescript@6.0.3) '@atcute/tid': 1.1.4 '@atcute/xrpc-server': 2.0.2(@atcute/cid@2.4.2)(@atcute/lexicons@2.0.3)(typescript@6.0.3) cac: 7.0.0 @@ -3075,7 +3072,6 @@ snapshots: optionalDependencies: wrangler: 4.123.0(@cloudflare/workers-types@5.20260815.1) transitivePeerDependencies: - - '@atcute/identity' - prettier - react - typescript diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 78fd92f..a44f21d 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -4,6 +4,3 @@ packages: allowBuilds: esbuild: true workerd: true - -patchedDependencies: - '@atmo-dev/contrail@0.17.0': patches/@atmo-dev__contrail@0.17.0.patch diff --git a/scripts/dev-stack.mjs b/scripts/dev-stack.mjs deleted file mode 100644 index 4b26d40..0000000 --- a/scripts/dev-stack.mjs +++ /dev/null @@ -1,77 +0,0 @@ -import { spawn } from 'node:child_process'; -import { selectContrail } from './select-contrail.mjs'; - -const children = new Set(); -let stopping = false; - -function spawnPnpm(args) { - const npmExecPath = process.env.npm_execpath; - const command = npmExecPath ? process.execPath : 'pnpm'; - const commandArgs = npmExecPath ? [npmExecPath, ...args] : args; - const child = spawn(command, commandArgs, { - stdio: 'inherit', - detached: process.platform !== 'win32' - }); - children.add(child); - child.once('exit', () => children.delete(child)); - return child; -} - -function stopChild(child, signal = 'SIGTERM') { - if (!child.pid || child.killed) return; - try { - if (process.platform === 'win32') child.kill(signal); - else process.kill(-child.pid, signal); - } catch { - // The process may already have exited. - } -} - -function stopAll(signal = 'SIGTERM') { - if (stopping) return; - stopping = true; - for (const child of children) stopChild(child, signal); -} - -for (const signal of ['SIGINT', 'SIGTERM']) { - process.on(signal, () => { - const exitCode = signal === 'SIGINT' ? 130 : 143; - stopAll(signal); - process.exitCode = exitCode; - setTimeout(() => process.exit(exitCode), 1_500).unref(); - }); -} - -// A first local run may backfill before Wrangler starts listening. Keep the -// standalone dev:local timeout short, but let the combined stack wait for it. -process.env.ATMO_LOCAL_API_WAIT_MS ??= String(15 * 60 * 1_000); - -const api = spawnPnpm(['--dir', 'api', 'run', 'dev']); -const apiStartupFailure = new Promise((_, reject) => { - api.once('exit', (code, signal) => { - reject(new Error(`Local API exited with ${signal ?? `code ${code}`}`)); - }); -}); - -try { - await Promise.race([selectContrail('local'), apiStartupFailure]); - - const web = spawnPnpm(['exec', 'vite', 'dev']); - const result = await Promise.race([ - new Promise((resolve) => - api.once('exit', (code, signal) => resolve({ service: 'API', code, signal })) - ), - new Promise((resolve) => - web.once('exit', (code, signal) => resolve({ service: 'web app', code, signal })) - ) - ]); - - if (!stopping && result.code !== 0) { - throw new Error(`${result.service} exited with ${result.signal ?? `code ${result.code}`}`); - } -} catch (cause) { - console.error(cause instanceof Error ? cause.message : cause); - process.exitCode = 1; -} finally { - stopAll(); -} diff --git a/scripts/ensure-api-dev-vars.mjs b/scripts/ensure-api-dev-vars.mjs deleted file mode 100644 index ef77a1c..0000000 --- a/scripts/ensure-api-dev-vars.mjs +++ /dev/null @@ -1,19 +0,0 @@ -import { readFile, writeFile } from 'node:fs/promises'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; - -const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); -const varsPath = path.join(root, 'api/.dev.vars'); -const endpointLine = 'CONTRAIL_PUBLIC_ENDPOINT="http://127.0.0.1:8787"'; - -let contents = ''; -try { - contents = await readFile(varsPath, 'utf8'); -} catch { - // The local vars file is created on first use and remains ignored. -} - -if (!/^CONTRAIL_PUBLIC_ENDPOINT=/m.test(contents)) { - const separator = contents && !contents.endsWith('\n') ? '\n' : ''; - await writeFile(varsPath, `${contents}${separator}${endpointLine}\n`); -} diff --git a/scripts/select-contrail.mjs b/scripts/select-contrail.mjs deleted file mode 100644 index 31505b5..0000000 --- a/scripts/select-contrail.mjs +++ /dev/null @@ -1,147 +0,0 @@ -import { spawn } from 'node:child_process'; -import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'; -import path from 'node:path'; -import { fileURLToPath, pathToFileURL } from 'node:url'; - -const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); -const activeDir = path.join(root, 'src/lib/contrail-active'); -const localConsumerRoot = path.join(root, '.contrail/local-consumer'); -const localLockPath = path.join(localConsumerRoot, 'contrail.lock.json'); -const localClientPath = path.join(localConsumerRoot, 'index.ts'); -const defaultLocalEndpoint = 'http://127.0.0.1:8787'; - -function normalizeEndpoint(value) { - return value.replace(/\/$/, ''); -} - -async function writeActiveModule(content, target) { - await rm(activeDir, { recursive: true, force: true }); - await mkdir(activeDir, { recursive: true }); - await writeFile(path.join(activeDir, 'index.ts'), content); - await mkdir(path.join(root, '.contrail'), { recursive: true }); - await writeFile(path.join(root, '.contrail/active-target'), `${target}\n`); -} - -async function selectProd() { - await writeActiveModule( - `// Generated by scripts/select-contrail.mjs. Do not edit.\nexport { contrail, contrailMethods } from '../contrail-targets/prod.js';\nexport const contrailTarget = 'prod' as const;\n`, - 'prod' - ); - console.log('Contrail target: production (https://api.atmo.watch)'); -} - -function assertLoopbackEndpoint(endpoint) { - const url = new URL(endpoint); - if (!['127.0.0.1', 'localhost', '::1', '[::1]'].includes(url.hostname)) { - throw new Error(`Local Contrail endpoint must be loopback, received ${endpoint}`); - } -} - -async function waitForLocalApi(endpoint) { - const waitMs = Number(process.env.ATMO_LOCAL_API_WAIT_MS ?? 30_000); - const deadline = Date.now() + waitMs; - const discoveryUrl = `${endpoint}/.well-known/contrail`; - - while (Date.now() < deadline) { - try { - const response = await fetch(discoveryUrl, { signal: AbortSignal.timeout(1_500) }); - if (response.ok) return; - } catch { - // The local API may still be starting. - } - await new Promise((resolve) => setTimeout(resolve, 250)); - } - - throw new Error( - `Local Contrail API did not become ready at ${endpoint}. Run "pnpm api:dev" first or use "pnpm dev:stack".` - ); -} - -function run(command, args) { - return new Promise((resolve, reject) => { - const child = spawn(command, args, { cwd: root, stdio: 'inherit' }); - child.once('error', reject); - child.once('exit', (code, signal) => { - if (code === 0) resolve(); - else reject(new Error(`${command} exited with ${signal ?? `code ${code}`}`)); - }); - }); -} - -async function connectLocal(endpoint) { - let update = false; - try { - const lock = JSON.parse(await readFile(localLockPath, 'utf8')); - if (normalizeEndpoint(lock.endpoint) === endpoint) update = true; - else await rm(localConsumerRoot, { recursive: true, force: true }); - } catch { - await rm(localConsumerRoot, { recursive: true, force: true }); - } - - await mkdir(localConsumerRoot, { recursive: true }); - const contrailBin = path.join( - root, - 'node_modules/.bin', - process.platform === 'win32' ? 'contrail.cmd' : 'contrail' - ); - const args = [ - 'connect', - endpoint, - '--root', - localConsumerRoot, - '--out', - 'lexicons', - '--lock', - 'contrail.lock.json', - '--client', - 'index.ts', - '--client-types', - 'types/index.ts', - '--allow-insecure-http' - ]; - if (update) args.push('--update'); - await run(contrailBin, args); - - // The app's tracked API types represent the source tree. Reusing them avoids - // loading a second set of ambient XRPC declarations from the ignored local bundle. - const client = await readFile(localClientPath, 'utf8'); - const patchedClient = client.replace( - 'import type {} from "./types/index.js";', - 'import type {} from "../../src/lib/contrail/types/index.js";' - ); - if (patchedClient === client) { - throw new Error('Could not redirect the generated local Contrail client types'); - } - await writeFile(localClientPath, patchedClient); -} - -async function selectLocal() { - const endpoint = normalizeEndpoint(process.env.ATMO_LOCAL_API_URL ?? defaultLocalEndpoint); - assertLoopbackEndpoint(endpoint); - await waitForLocalApi(endpoint); - await connectLocal(endpoint); - - const lock = JSON.parse(await readFile(localLockPath, 'utf8')); - const protectedMethods = (lock.serviceAuth?.methods ?? []).map((method) => method.id); - const methods = [...new Set([...lock.methods, ...protectedMethods])].sort(); - await writeActiveModule( - `// Generated by scripts/select-contrail.mjs. Do not edit.\nexport { contrail } from '../../../.contrail/local-consumer/index.js';\nexport const contrailMethods = ${JSON.stringify(methods, null, '\t')} as const;\nexport const contrailTarget = 'local' as const;\n`, - 'local' - ); - console.log(`Contrail target: local (${endpoint})`); -} - -export async function selectContrail(target = process.env.ATMO_API_TARGET ?? 'prod') { - if (target === 'prod') return selectProd(); - if (target === 'local') return selectLocal(); - throw new Error(`Unknown Contrail target "${target}"; expected "prod" or "local"`); -} - -const isMain = - process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href; -if (isMain) { - selectContrail(process.argv[2]).catch((cause) => { - console.error(cause instanceof Error ? cause.message : cause); - process.exitCode = 1; - }); -} diff --git a/scripts/update-prod-contrail.mjs b/scripts/update-prod-contrail.mjs deleted file mode 100644 index 78a3112..0000000 --- a/scripts/update-prod-contrail.mjs +++ /dev/null @@ -1,79 +0,0 @@ -import { spawn } from 'node:child_process'; -import { readFile, writeFile } from 'node:fs/promises'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { format } from 'prettier'; -import { selectContrail } from './select-contrail.mjs'; - -const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); -const endpoint = 'https://api.atmo.watch'; -const clientPath = path.join(root, 'src/lib/contrail/index.ts'); -const lockPath = path.join(root, 'contrail.lock.json'); -const prodTargetPath = path.join(root, 'src/lib/contrail-targets/prod.ts'); - -function run(command, args) { - return new Promise((resolve, reject) => { - const child = spawn(command, args, { cwd: root, stdio: 'inherit' }); - child.once('error', reject); - child.once('exit', (code, signal) => { - if (code === 0) resolve(); - else reject(new Error(`${command} exited with ${signal ?? `code ${code}`}`)); - }); - }); -} - -const contrailBin = path.join( - root, - 'node_modules/.bin', - process.platform === 'win32' ? 'contrail.cmd' : 'contrail' -); - -const sourceLock = JSON.parse(await readFile(lockPath, 'utf8')); -const discoveryResponse = await fetch(`${endpoint}/.well-known/contrail`); -if (!discoveryResponse.ok) { - throw new Error(`Could not read the production Contrail manifest (${discoveryResponse.status})`); -} -const discovery = await discoveryResponse.json(); -if (discovery.contract?.digest !== sourceLock.contractDigest) { - throw new Error( - `Production still advertises ${discovery.contract?.digest ?? 'no contract digest'}, but the source contract is ${sourceLock.contractDigest}. Deploy the API first.` - ); -} - -await run(contrailBin, [ - 'connect', - endpoint, - '--root', - root, - '--out', - 'src/lib/contrail/lexicons', - '--lock', - 'contrail.lock.json', - '--client', - 'src/lib/contrail/index.ts', - '--client-types', - 'src/lib/contrail/types/index.ts', - '--update' -]); - -const lock = JSON.parse(await readFile(lockPath, 'utf8')); -const protectedMethods = (lock.serviceAuth?.methods ?? []).map((method) => method.id); -const methods = [...new Set([...lock.methods, ...protectedMethods])].sort(); -let client = await readFile(clientPath, 'utf8'); -client = client - .replace( - '// Generated by `contrail connect`. Re-run the command to update; do not edit.', - '// Generated from the deployed Contrail contract by scripts/update-prod-contrail.mjs.' - ) - .replace( - 'import type {} from "./types/index.js";', - "import type {} from '$lib/contrail/types/index.js';" - ) - .replace( - 'export const contrail = createPublicServiceClient({', - `export const contrailMethods = ${JSON.stringify(methods, null, '\t')} as const;\n\nexport const contrail = createPublicServiceClient({` - ); - -await writeFile(prodTargetPath, await format(client, { filepath: prodTargetPath })); -await selectContrail('prod'); -console.log(`Updated tracked production Contrail contract from ${endpoint}`); diff --git a/src/lib/atproto/index.ts b/src/lib/atproto/index.ts index 19bdeb6..bd31184 100644 --- a/src/lib/atproto/index.ts +++ b/src/lib/atproto/index.ts @@ -4,7 +4,7 @@ import { createAtprotoAuth } from '@svelte-atproto/oauth/server'; import { cloudflareKV } from '@svelte-atproto/oauth/server/stores/cloudflare'; import { building } from '$app/environment'; import { env } from '$env/dynamic/private'; -import { contrail } from '$lib/contrail-active'; +import { contrail } from '$lib/contrail-client.server'; // To enable signup, add: signupPDS: 'https://your-pds.example/' export const atproto = createAtprotoAuth({ @@ -12,9 +12,12 @@ export const atproto = createAtprotoAuth({ // Cloudflare runtime secrets are unavailable while SvelteKit analyses the build. cookieSecret: env.COOKIE_SECRET || (building ? 'build-only-cookie-secret' : undefined), clientAssertionKey: env.CLIENT_ASSERTION_KEY, - scope: - 'atproto repo:social.popfeed.feed.review repo:social.popfeed.feed.comment repo:social.popfeed.feed.like repo:social.popfeed.feed.list repo:social.popfeed.feed.listItem blob:*/* ' + - contrail.scope, + scope: [ + 'atproto repo:social.popfeed.feed.review repo:social.popfeed.feed.comment repo:social.popfeed.feed.like repo:social.popfeed.feed.list repo:social.popfeed.feed.listItem blob:*/*', + contrail.scope + ] + .filter(Boolean) + .join(' '), sessions: cloudflareKV('OAUTH_SESSIONS'), states: cloudflareKV('OAUTH_STATES', { ttl: 600 }) }); diff --git a/src/lib/contrail-client.server.ts b/src/lib/contrail-client.server.ts new file mode 100644 index 0000000..92aa831 --- /dev/null +++ b/src/lib/contrail-client.server.ts @@ -0,0 +1,16 @@ +import { env } from '$env/dynamic/private'; +import { + contrail as productionContrail, + contrailApi, + contrailMethods as productionContrailMethods, + createLocalContrailClient +} from '$lib/contrail/index.js'; + +const localEndpoint = env.CONTRAIL_URL; + +export const contrail = localEndpoint + ? createLocalContrailClient(localEndpoint) + : productionContrail; +export const contrailMethods = localEndpoint + ? contrailApi.serviceMethods + : productionContrailMethods; diff --git a/src/lib/contrail-targets/prod.ts b/src/lib/contrail-targets/prod.ts deleted file mode 100644 index 36b0620..0000000 --- a/src/lib/contrail-targets/prod.ts +++ /dev/null @@ -1,55 +0,0 @@ -// Generated from the deployed Contrail contract by scripts/update-prod-contrail.mjs. -import { createPublicServiceClient } from "@atmo-dev/contrail/client"; -import type {} from "$lib/contrail/types/index.js"; - -export const contrailMethods = [ - "watch.atmo.comment.getRecord", - "watch.atmo.comment.listRecords", - "watch.atmo.getCursor", - "watch.atmo.getProfile", - "watch.atmo.like.getRecord", - "watch.atmo.like.listRecords", - "watch.atmo.list.getRecord", - "watch.atmo.list.listRecords", - "watch.atmo.listItem.getRecord", - "watch.atmo.listItem.listRecords", - "watch.atmo.notifyOfUpdate", - "watch.atmo.review.getRecord", - "watch.atmo.review.listRecords", - "watch.atmo.review.listWrittenRecords", -] as const; - -export const contrail = createPublicServiceClient({ - endpoint: "https://api.atmo.watch", - contractDigest: - "sha256:edfc8edad45272bc6a66e56d2ea4bc61d1d7f3724304fdd492f9486c33d4032c", - serviceDid: "did:web:api.atmo.watch#contrail", - scope: - "rpc?lxm=watch.atmo.notifyOfUpdate&aud=did:web:api.atmo.watch%23contrail", - serviceMethods: [ - "watch.atmo.comment.getRecord", - "watch.atmo.comment.listRecords", - "watch.atmo.getCursor", - "watch.atmo.getProfile", - "watch.atmo.like.getRecord", - "watch.atmo.like.listRecords", - "watch.atmo.list.getRecord", - "watch.atmo.list.listRecords", - "watch.atmo.listItem.getRecord", - "watch.atmo.listItem.listRecords", - "watch.atmo.notifyOfUpdate", - "watch.atmo.review.getRecord", - "watch.atmo.review.listRecords", - "watch.atmo.review.listWrittenRecords", - ], - collections: [ - "app.bsky.actor.profile", - "social.popfeed.actor.profile", - "social.popfeed.feed.comment", - "social.popfeed.feed.like", - "social.popfeed.feed.list", - "social.popfeed.feed.listItem", - "social.popfeed.feed.review", - ], - notifyMethod: "watch.atmo.notifyOfUpdate", -}); diff --git a/src/lib/contrail/index.ts b/src/lib/contrail/index.ts index 842e420..09b9133 100644 --- a/src/lib/contrail/index.ts +++ b/src/lib/contrail/index.ts @@ -1,12 +1,49 @@ // Generated by `contrail connect`. Re-run the command to update; do not edit. import { createPublicServiceClient } from "@atmo-dev/contrail/client"; +import type { PublicServiceClientOptions } from "@atmo-dev/contrail/client"; import type {} from "./types/index.js"; -export const contrail = createPublicServiceClient({ +export const contrailApi = { + namespace: "watch.atmo", + serviceMethods: [ + "watch.atmo.comment.getRecord", + "watch.atmo.comment.listRecords", + "watch.atmo.getCursor", + "watch.atmo.getProfile", + "watch.atmo.like.getRecord", + "watch.atmo.like.listRecords", + "watch.atmo.list.getRecord", + "watch.atmo.list.listRecords", + "watch.atmo.listItem.getRecord", + "watch.atmo.listItem.listRecords", + "watch.atmo.notifyOfUpdate", + "watch.atmo.review.getRecord", + "watch.atmo.review.listRecords", + "watch.atmo.review.listWrittenRecords", + ], + protectedMethods: [ + "watch.atmo.notifyOfUpdate", + ], + collections: [ + "app.bsky.actor.profile", + "social.popfeed.actor.profile", + "social.popfeed.feed.comment", + "social.popfeed.feed.like", + "social.popfeed.feed.list", + "social.popfeed.feed.listItem", + "social.popfeed.feed.review", + ], + notifyMethod: "watch.atmo.notifyOfUpdate", +} as const; + +export const contrailTarget = { endpoint: "https://api.atmo.watch", - contractDigest: "sha256:edfc8edad45272bc6a66e56d2ea4bc61d1d7f3724304fdd492f9486c33d4032c", - serviceDid: "did:web:api.atmo.watch#contrail", - scope: "rpc?lxm=watch.atmo.notifyOfUpdate&aud=did:web:api.atmo.watch%23contrail", + serviceDid: "did:web:api.atmo.watch", + serviceAudience: "did:web:api.atmo.watch#contrail", + scope: "rpc?aud=did:web:api.atmo.watch%23contrail&lxm=watch.atmo.notifyOfUpdate", + protectedMethods: [ + "watch.atmo.notifyOfUpdate", + ], serviceMethods: [ "watch.atmo.comment.getRecord", "watch.atmo.comment.listRecords", @@ -33,4 +70,41 @@ export const contrail = createPublicServiceClient({ "social.popfeed.feed.review", ], notifyMethod: "watch.atmo.notifyOfUpdate", -}); +} as const; + +export const contrailMethods = contrailTarget.serviceMethods; + +export type ContrailTarget = Pick< + PublicServiceClientOptions, + "endpoint" | "allowInsecureHttp" | "serviceDid" | "serviceAudience" | "scope" | "protectedMethods" | "serviceMethods" | "collections" +> & { + notifyMethod?: PublicServiceClientOptions["notifyMethod"] | null; +}; + +export function createContrailClient(target: ContrailTarget = contrailTarget) { + const { notifyMethod: targetNotifyMethod, ...runtimeTarget } = target; + const notifyMethod = + targetNotifyMethod === undefined + ? contrailApi.notifyMethod + : targetNotifyMethod; + return createPublicServiceClient({ + ...runtimeTarget, + serviceMethods: target.serviceMethods ?? contrailApi.serviceMethods, + collections: target.collections ?? contrailApi.collections, + ...(notifyMethod ? { notifyMethod } : {}), + }); +} + +export function createLocalContrailClient( + endpoint = "http://127.0.0.1:8787", +) { + return createContrailClient({ + endpoint, + allowInsecureHttp: true, + serviceMethods: contrailApi.serviceMethods, + collections: contrailApi.collections, + notifyMethod: contrailApi.notifyMethod, + }); +} + +export const contrail = createContrailClient(); diff --git a/src/lib/contrail/lexicons/source/app/bsky/actor/profile.json b/src/lib/contrail/lexicons/source/app/bsky/actor/profile.json new file mode 100644 index 0000000..1f357c5 --- /dev/null +++ b/src/lib/contrail/lexicons/source/app/bsky/actor/profile.json @@ -0,0 +1,75 @@ +{ + "id": "app.bsky.actor.profile", + "defs": { + "main": { + "key": "literal:self", + "type": "record", + "record": { + "type": "object", + "properties": { + "avatar": { + "type": "blob", + "accept": [ + "image/jpeg", + "image/png" + ], + "maxSize": 1000000, + "description": "Small image to be displayed next to posts from account. AKA, 'profile picture'" + }, + "banner": { + "type": "blob", + "accept": [ + "image/jpeg", + "image/png" + ], + "maxSize": 1000000, + "description": "Larger horizontal image to display behind profile view." + }, + "labels": { + "refs": [ + "com.atproto.label.defs#selfLabels" + ], + "type": "union", + "description": "Self-label values, specific to the Bluesky application, on the overall account." + }, + "website": { + "type": "string", + "format": "uri" + }, + "pronouns": { + "type": "string", + "maxLength": 200, + "description": "Free-form pronouns text.", + "maxGraphemes": 20 + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "pinnedPost": { + "ref": "com.atproto.repo.strongRef", + "type": "ref" + }, + "description": { + "type": "string", + "maxLength": 2560, + "description": "Free-form profile description text.", + "maxGraphemes": 256 + }, + "displayName": { + "type": "string", + "maxLength": 640, + "maxGraphemes": 64 + }, + "joinedViaStarterPack": { + "ref": "com.atproto.repo.strongRef", + "type": "ref" + } + } + }, + "description": "A declaration of a Bluesky account profile." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/src/lib/contrail/lexicons/source/com/atproto/label/defs.json b/src/lib/contrail/lexicons/source/com/atproto/label/defs.json new file mode 100644 index 0000000..35de120 --- /dev/null +++ b/src/lib/contrail/lexicons/source/com/atproto/label/defs.json @@ -0,0 +1,190 @@ +{ + "id": "com.atproto.label.defs", + "defs": { + "label": { + "type": "object", + "required": [ + "cts", + "src", + "uri", + "val" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid", + "description": "Optionally, CID specifying the specific version of 'uri' resource this label applies to." + }, + "cts": { + "type": "string", + "format": "datetime", + "description": "Timestamp when this label was created." + }, + "exp": { + "type": "string", + "format": "datetime", + "description": "Timestamp at which this label expires (no longer applies)." + }, + "neg": { + "type": "boolean", + "description": "If true, this is a negation label, overwriting a previous label." + }, + "sig": { + "type": "bytes", + "description": "Signature of dag-cbor encoded label." + }, + "src": { + "type": "string", + "format": "did", + "description": "DID of the actor who created this label." + }, + "uri": { + "type": "string", + "format": "uri", + "description": "AT URI of the record, repository (account), or other resource that this label applies to." + }, + "val": { + "type": "string", + "maxLength": 128, + "description": "The short string name of the value or type of this label." + }, + "ver": { + "type": "integer", + "description": "The AT Protocol version of the label object." + } + }, + "description": "Metadata tag on an atproto resource (eg, repo or record)." + }, + "selfLabel": { + "type": "object", + "required": [ + "val" + ], + "properties": { + "val": { + "type": "string", + "maxLength": 128, + "description": "The short string name of the value or type of this label." + } + }, + "description": "Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel." + }, + "labelValue": { + "type": "string", + "knownValues": [ + "!hide", + "!no-unauthenticated", + "!warn", + "bot", + "graphic-media", + "nudity", + "porn", + "sexual" + ] + }, + "selfLabels": { + "type": "object", + "required": [ + "values" + ], + "properties": { + "values": { + "type": "array", + "items": { + "ref": "#selfLabel", + "type": "ref" + }, + "maxLength": 10 + } + }, + "description": "Metadata tags on an atproto record, published by the author within the record." + }, + "labelValueDefinition": { + "type": "object", + "required": [ + "blurs", + "identifier", + "locales", + "severity" + ], + "properties": { + "blurs": { + "type": "string", + "description": "What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.", + "knownValues": [ + "content", + "media", + "none" + ] + }, + "locales": { + "type": "array", + "items": { + "ref": "#labelValueDefinitionStrings", + "type": "ref" + } + }, + "severity": { + "type": "string", + "description": "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.", + "knownValues": [ + "alert", + "inform", + "none" + ] + }, + "adultOnly": { + "type": "boolean", + "description": "Does the user need to have adult content enabled in order to configure this label?" + }, + "identifier": { + "type": "string", + "maxLength": 100, + "description": "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).", + "maxGraphemes": 100 + }, + "defaultSetting": { + "type": "string", + "default": "warn", + "description": "The default setting for this label.", + "knownValues": [ + "hide", + "ignore", + "warn" + ] + } + }, + "description": "Declares a label value and its expected interpretations and behaviors." + }, + "labelValueDefinitionStrings": { + "type": "object", + "required": [ + "description", + "lang", + "name" + ], + "properties": { + "lang": { + "type": "string", + "format": "language", + "description": "The code of the language these strings are written in." + }, + "name": { + "type": "string", + "maxLength": 640, + "description": "A short human-readable name for the label.", + "maxGraphemes": 64 + }, + "description": { + "type": "string", + "maxLength": 100000, + "description": "A longer description of what the label means and why it might be applied.", + "maxGraphemes": 10000 + } + }, + "description": "Strings which describe the label in the UI, localized into a specific language." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/src/lib/contrail/lexicons/source/com/atproto/repo/strongRef.json b/src/lib/contrail/lexicons/source/com/atproto/repo/strongRef.json new file mode 100644 index 0000000..aa3b02a --- /dev/null +++ b/src/lib/contrail/lexicons/source/com/atproto/repo/strongRef.json @@ -0,0 +1,25 @@ +{ + "id": "com.atproto.repo.strongRef", + "defs": { + "main": { + "type": "object", + "required": [ + "cid", + "uri" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "uri": { + "type": "string", + "format": "at-uri" + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1, + "description": "A URI with a content-hash fingerprint." +} diff --git a/src/lib/contrail/lexicons/source/social/popfeed/actor/profile.json b/src/lib/contrail/lexicons/source/social/popfeed/actor/profile.json new file mode 100644 index 0000000..e34eb53 --- /dev/null +++ b/src/lib/contrail/lexicons/source/social/popfeed/actor/profile.json @@ -0,0 +1,72 @@ +{ + "defs": { + "externalLink": { + "properties": { + "service": { + "description": "Name of the external service (e.g., 'Twitter', 'GitHub').", + "maxLength": 100, + "type": "string" + }, + "url": { + "description": "URL to the user's profile on the external service.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "service", + "url" + ], + "type": "object" + }, + "main": { + "description": "A declaration of a user's profile information.", + "key": "literal:self", + "record": { + "properties": { + "banner": { + "accept": [ + "image/png", + "image/jpeg" + ], + "description": "Larger horizontal image to display behind profile view. Have not fully cut over yet so may not be shown", + "maxSize": 1000000, + "type": "blob" + }, + "bannerUrl": { + "description": "Optional URL to a banner image. DEPRECATED - soon to be ignored", + "format": "uri", + "type": "string" + }, + "createdAt": { + "format": "datetime", + "type": "string" + }, + "description": { + "description": "Free-form profile description text.", + "maxGraphemes": 256, + "maxLength": 2560, + "type": "string" + }, + "displayName": { + "maxGraphemes": 64, + "maxLength": 640, + "type": "string" + }, + "externalLinks": { + "description": "List of external links to the user's profiles on other services.", + "items": { + "ref": "#externalLink", + "type": "ref" + }, + "type": "array" + } + }, + "type": "object" + }, + "type": "record" + } + }, + "id": "social.popfeed.actor.profile", + "lexicon": 1 +} diff --git a/src/lib/contrail/lexicons/source/social/popfeed/feed/comment.json b/src/lib/contrail/lexicons/source/social/popfeed/feed/comment.json new file mode 100644 index 0000000..fd6e3ed --- /dev/null +++ b/src/lib/contrail/lexicons/source/social/popfeed/feed/comment.json @@ -0,0 +1,59 @@ +{ + "defs": { + "main": { + "description": "Record containing a comment on a review.", + "key": "tid", + "record": { + "properties": { + "createdAt": { + "description": "Client-declared timestamp when this comment was created.", + "format": "datetime", + "type": "string" + }, + "facets": { + "description": "Annotations of text (mentions, URLs, hashtags, etc).", + "items": { + "ref": "social.popfeed.richtext.facet", + "type": "ref" + }, + "type": "array" + }, + "rootUri": { + "description": "The URI of the root entity of the comment thread, if applicable.", + "format": "uri", + "type": "string" + }, + "subjectType": { + "description": "The type of subject being commented on, e.g., 'review'.", + "enum": [ + "review", + "list", + "post", + "comment" + ], + "type": "string" + }, + "subjectUri": { + "description": "The URI of the review being commented on.", + "format": "uri", + "type": "string" + }, + "text": { + "description": "The primary comment content.", + "maxLength": 1000, + "type": "string" + } + }, + "required": [ + "text", + "subjectUri", + "createdAt" + ], + "type": "object" + }, + "type": "record" + } + }, + "id": "social.popfeed.feed.comment", + "lexicon": 1 +} diff --git a/src/lib/contrail/lexicons/source/social/popfeed/feed/like.json b/src/lib/contrail/lexicons/source/social/popfeed/feed/like.json new file mode 100644 index 0000000..a47081e --- /dev/null +++ b/src/lib/contrail/lexicons/source/social/popfeed/feed/like.json @@ -0,0 +1,40 @@ +{ + "defs": { + "main": { + "description": "Record declaring a 'like' of a review.", + "key": "tid", + "record": { + "properties": { + "createdAt": { + "description": "Client-declared timestamp when this like was created.", + "format": "datetime", + "type": "string" + }, + "subjectType": { + "description": "The type of subject being liked, e.g., 'review'.", + "enum": [ + "review", + "list", + "post", + "comment" + ], + "type": "string" + }, + "subjectUri": { + "description": "The type of subject being liked, e.g., 'review'.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "subjectUri", + "createdAt" + ], + "type": "object" + }, + "type": "record" + } + }, + "id": "social.popfeed.feed.like", + "lexicon": 1 +} diff --git a/src/lib/contrail/lexicons/source/social/popfeed/feed/list.json b/src/lib/contrail/lexicons/source/social/popfeed/feed/list.json new file mode 100644 index 0000000..9e19527 --- /dev/null +++ b/src/lib/contrail/lexicons/source/social/popfeed/feed/list.json @@ -0,0 +1,59 @@ +{ + "defs": { + "main": { + "key": "tid", + "record": { + "properties": { + "createdAt": { + "description": "The timestamp when the list was created.", + "format": "datetime", + "type": "string" + }, + "description": { + "description": "Optional description of the list.", + "maxLength": 500, + "type": "string" + }, + "itemOrder": { + "description": "An array of item uris in the order they appear in the list. Stored separately from the items themselves to allow for efficient reordering without needing to update each item record.", + "items": { + "format": "uri", + "type": "string" + }, + "type": "array" + }, + "listType": { + "description": "The type of list, e.g., 'watchlist', 'favorites', 'to-read', etc.", + "maxLength": 50, + "type": "string" + }, + "name": { + "description": "The name of the list.", + "maxLength": 100, + "type": "string" + }, + "ordered": { + "description": "Indicates whether the list is ordered or unordered.", + "type": "boolean" + }, + "tags": { + "description": "Optional array of tags for categorizing or describing the list.", + "items": { + "maxLength": 50, + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "name", + "createdAt" + ], + "type": "object" + }, + "type": "record" + } + }, + "id": "social.popfeed.feed.list", + "lexicon": 1 +} diff --git a/src/lib/contrail/lexicons/source/social/popfeed/feed/listItem.json b/src/lib/contrail/lexicons/source/social/popfeed/feed/listItem.json new file mode 100644 index 0000000..8d4908c --- /dev/null +++ b/src/lib/contrail/lexicons/source/social/popfeed/feed/listItem.json @@ -0,0 +1,282 @@ +{ + "defs": { + "bookProgress": { + "description": "Tracks reading/listening progress for a book or book series item.", + "properties": { + "currentChapter": { + "description": "Current chapter number (optional alternative to pages).", + "type": "integer" + }, + "currentPage": { + "description": "Current page number (if applicable).", + "type": "integer" + }, + "percent": { + "description": "Optional explicit progress percentage (0–100).", + "type": "integer" + }, + "status": { + "description": "Overall progress state.", + "enum": [ + "in_progress", + "completed", + "paused" + ], + "type": "string" + }, + "totalChapters": { + "description": "Total chapters (if known).", + "type": "integer" + }, + "totalPages": { + "description": "Total pages (if known).", + "type": "integer" + }, + "updatedAt": { + "description": "Last time progress was updated.", + "format": "datetime", + "type": "string" + } + }, + "required": [ + "status", + "updatedAt" + ], + "type": "object" + }, + "identifiers": { + "properties": { + "asin": { + "description": "Amazon Standard ID", + "type": "string" + }, + "episodeNumber": { + "description": "Episode number for TV shows or series", + "type": "integer" + }, + "igdbId": { + "description": "IGDB ID for games", + "type": "string" + }, + "imdbId": { + "description": "IMDb ID", + "type": "string" + }, + "isbn10": { + "description": "ISBN-10 for books", + "type": "string" + }, + "isbn13": { + "description": "ISBN-13 for books", + "type": "string" + }, + "mbId": { + "description": "DEPRECATED - soon to be ignored", + "type": "string" + }, + "mbReleaseId": { + "description": "MusicBrainz ID for specific releases (albums, EPs, tracks)", + "type": "string" + }, + "other": { + "description": "Other external ID", + "type": "string" + }, + "parentMbReleaseId": { + "description": "MusicBrainz ID for parent release (e.g., album for a track)", + "type": "string" + }, + "seasonNumber": { + "description": "Season number for TV shows or series", + "type": "integer" + }, + "tmdbId": { + "description": "TMDb ID for movies/TV shows", + "type": "string" + }, + "tmdbTvSeriesId": { + "description": "TMDb ID for TV series", + "type": "string" + } + }, + "type": "object" + }, + "main": { + "key": "tid", + "record": { + "properties": { + "addedAt": { + "description": "Timestamp when the creative work was added to the list.", + "format": "datetime", + "type": "string" + }, + "backdrop": { + "accept": [ + "image/*" + ], + "maxSize": 4000000, + "type": "blob" + }, + "backdropUrl": { + "description": "Backdrop image URL for the creative work. Soon to be deprecated in favor of the 'backdrop' blob field", + "format": "uri", + "type": "string" + }, + "bookProgress": { + "description": "Reading progress for books or book series (also supports audiobooks via time fields).", + "ref": "#bookProgress", + "type": "ref" + }, + "completedAt": { + "description": "Timestamp when the user finished the creative work. Most relevant for books, TV shows, and video games.", + "format": "datetime", + "type": "string" + }, + "creativeWorkType": { + "description": "The type of creative work this item represents.", + "enum": [ + "movie", + "tv_show", + "video_game", + "album", + "book", + "book_series", + "episode", + "album", + "ep", + "tv_season", + "tv_episode", + "track" + ], + "type": "string" + }, + "description": { + "description": "Optional description or commentary for the list item.", + "maxLength": 5000, + "type": "string" + }, + "genres": { + "items": { + "maxLength": 50, + "type": "string" + }, + "type": "array" + }, + "identifiers": { + "description": "External identifiers for the creative work.", + "ref": "#identifiers", + "type": "ref" + }, + "listType": { + "description": "The type of list, e.g., 'watchlist', 'favorites', 'to-read', etc.", + "maxLength": 50, + "type": "string" + }, + "listUri": { + "description": "URI of the list where the item is stored.", + "format": "uri", + "type": "string" + }, + "mainCredit": { + "description": "Main actor, director, author, or artist of the creative work.", + "maxLength": 1000, + "type": "string" + }, + "mainCreditRole": { + "description": "The role of the main credit.", + "enum": [ + "director", + "author", + "artist", + "showrunner", + "lead_actor", + "creator", + "studio", + "publisher", + "developer", + "performer", + "network" + ], + "type": "string" + }, + "position": { + "description": "DEPRECATED", + "type": "integer" + }, + "poster": { + "accept": [ + "image/*" + ], + "maxSize": 2000000, + "type": "blob" + }, + "posterUrl": { + "description": "Poster or cover image URL for the creative work. Soon to be deprecated in favor of the 'poster' blob field", + "format": "uri", + "type": "string" + }, + "releaseDate": { + "description": "Release date of the creative work.", + "format": "datetime", + "type": "string" + }, + "startedAt": { + "description": "Timestamp when the user started the creative work. Most relevant for books, TV shows, and video games.", + "format": "datetime", + "type": "string" + }, + "status": { + "knownValues": [ + "#finished", + "#in_progress", + "#backlog", + "#abandoned" + ], + "type": "string" + }, + "title": { + "description": "Title of the creative work.", + "maxLength": 1000, + "type": "string" + }, + "watchedEpisodes": { + "description": "For TV shows, the episodes the user has watched.", + "items": { + "ref": "#watchedEpisode", + "type": "ref" + }, + "type": "array" + } + }, + "required": [ + "identifiers", + "creativeWorkType", + "addedAt", + "listUri" + ], + "type": "object" + }, + "type": "record" + }, + "watchedEpisode": { + "properties": { + "episodeNumber": { + "type": "integer" + }, + "seasonNumber": { + "type": "integer" + }, + "tmdbId": { + "type": "string" + } + }, + "required": [ + "seasonNumber", + "episodeNumber" + ], + "type": "object" + } + }, + "id": "social.popfeed.feed.listItem", + "lexicon": 1 +} diff --git a/src/lib/contrail/lexicons/source/social/popfeed/feed/review.json b/src/lib/contrail/lexicons/source/social/popfeed/feed/review.json new file mode 100644 index 0000000..2dbf6d7 --- /dev/null +++ b/src/lib/contrail/lexicons/source/social/popfeed/feed/review.json @@ -0,0 +1,212 @@ +{ + "defs": { + "crossPosts": { + "properties": { + "bluesky": { + "description": "The URI of the original Bluesky post being cross-posted.", + "format": "uri", + "type": "string" + }, + "leaflet": { + "description": "The URI of the original Leaflet post being cross-posted.", + "format": "uri", + "type": "string" + } + }, + "type": "object" + }, + "identifiers": { + "properties": { + "asin": { + "type": "string" + }, + "episodeNumber": { + "description": "Episode number for TV shows or series", + "type": "integer" + }, + "igdbId": { + "type": "string" + }, + "imdbId": { + "type": "string" + }, + "isbn10": { + "type": "string" + }, + "isbn13": { + "type": "string" + }, + "mbId": { + "description": "DEPRECATED - soon to be ignored", + "type": "string" + }, + "mbReleaseId": { + "description": "MusicBrainz ID for specific releases (albums, EPs, tracks)", + "type": "string" + }, + "other": { + "type": "string" + }, + "parentMbReleaseId": { + "description": "MusicBrainz ID for parent release (e.g., album for a track)", + "type": "string" + }, + "seasonNumber": { + "description": "Season number for TV shows or series", + "type": "integer" + }, + "tmdbId": { + "type": "string" + }, + "tmdbTvSeriesId": { + "description": "TMDb ID for TV series", + "type": "string" + } + }, + "type": "object" + }, + "main": { + "key": "tid", + "record": { + "properties": { + "backdrop": { + "accept": [ + "image/*" + ], + "description": "Backdrop image for the review post. Have not fully cut over yet so may not be shown", + "maxSize": 4000000, + "type": "blob" + }, + "backdropUrl": { + "description": "Backdrop image URL for the creative work. Soon to be deprecated in favor of the 'backdrop' blob field", + "format": "uri", + "type": "string" + }, + "containsSpoilers": { + "description": "Indicates if the review contains spoilers.", + "type": "boolean" + }, + "createdAt": { + "format": "datetime", + "type": "string" + }, + "creativeWorkType": { + "enum": [ + "movie", + "tv_show", + "video_game", + "album", + "book", + "book_series", + "episode", + "album", + "ep", + "tv_season", + "tv_episode", + "track" + ], + "type": "string" + }, + "crossPosts": { + "description": "Information about cross-posting to other platforms.", + "ref": "#crossPosts", + "type": "ref" + }, + "facets": { + "description": "Annotations of text (mentions, URLs, hashtags, etc)", + "items": { + "ref": "social.popfeed.richtext.facet", + "type": "ref" + }, + "type": "array" + }, + "genres": { + "items": { + "maxLength": 50, + "type": "string" + }, + "type": "array" + }, + "identifiers": { + "ref": "#identifiers", + "type": "ref" + }, + "isRevisit": { + "description": "Indicates if the user has experienced this work before (re-watch, re-read, replay).", + "type": "boolean" + }, + "mainCredit": { + "description": "Main actor, director, author, or artist of the creative work.", + "maxLength": 1000, + "type": "string" + }, + "mainCreditRole": { + "description": "The role of the main credit.", + "enum": [ + "director", + "author", + "artist", + "showrunner", + "lead_actor", + "creator", + "studio", + "publisher", + "developer", + "performer", + "network" + ], + "type": "string" + }, + "poster": { + "accept": [ + "image/*" + ], + "maxSize": 2000000, + "type": "blob" + }, + "posterUrl": { + "description": "Poster or cover image URL for the creative work. Soon to be deprecated in favor of the 'poster' blob field", + "format": "uri", + "type": "string" + }, + "rating": { + "maximum": 10, + "minimum": 0, + "type": "integer" + }, + "releaseDate": { + "description": "Release date of the creative work.", + "format": "datetime", + "type": "string" + }, + "tags": { + "items": { + "maxLength": 50, + "type": "string" + }, + "type": "array" + }, + "text": { + "maxLength": 100000, + "type": "string" + }, + "title": { + "description": "Title of the creative work.", + "maxLength": 1000, + "type": "string" + } + }, + "required": [ + "identifiers", + "creativeWorkType", + "rating", + "createdAt" + ], + "type": "object" + }, + "type": "record" + } + }, + "id": "social.popfeed.feed.review", + "lexicon": 1 +} diff --git a/src/lib/contrail/lexicons/source/social/popfeed/richtext/facet.json b/src/lib/contrail/lexicons/source/social/popfeed/richtext/facet.json new file mode 100644 index 0000000..05cc46a --- /dev/null +++ b/src/lib/contrail/lexicons/source/social/popfeed/richtext/facet.json @@ -0,0 +1,89 @@ +{ + "defs": { + "byteSlice": { + "description": "Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets.", + "properties": { + "byteEnd": { + "minimum": 0, + "type": "integer" + }, + "byteStart": { + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "byteStart", + "byteEnd" + ], + "type": "object" + }, + "link": { + "description": "Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL.", + "properties": { + "uri": { + "format": "uri", + "type": "string" + } + }, + "required": [ + "uri" + ], + "type": "object" + }, + "main": { + "description": "Annotation of a sub-string within rich text.", + "properties": { + "features": { + "items": { + "refs": [ + "#mention", + "#link", + "#tag" + ], + "type": "union" + }, + "type": "array" + }, + "index": { + "ref": "#byteSlice", + "type": "ref" + } + }, + "required": [ + "index", + "features" + ], + "type": "object" + }, + "mention": { + "description": "Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID.", + "properties": { + "did": { + "format": "did", + "type": "string" + } + }, + "required": [ + "did" + ], + "type": "object" + }, + "tag": { + "description": "Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags').", + "properties": { + "tag": { + "maxGraphemes": 64, + "maxLength": 640, + "type": "string" + } + }, + "required": [ + "tag" + ], + "type": "object" + } + }, + "id": "social.popfeed.richtext.facet", + "lexicon": 1 +} diff --git a/src/lib/contrail/lexicons/source/watch/atmo/comment/getRecord.json b/src/lib/contrail/lexicons/source/watch/atmo/comment/getRecord.json new file mode 100644 index 0000000..f7e9183 --- /dev/null +++ b/src/lib/contrail/lexicons/source/watch/atmo/comment/getRecord.json @@ -0,0 +1,113 @@ +{ + "lexicon": 1, + "id": "watch.atmo.comment.getRecord", + "defs": { + "main": { + "type": "query", + "description": "Get a social.popfeed.feed.comment 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 indexed profile and identity information" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "uri", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "social.popfeed.feed.comment#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "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" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "union", + "refs": [ + "app.bsky.actor.profile#main", + "social.popfeed.actor.profile#main" + ] + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + } + } + } + } +} diff --git a/src/lib/contrail/lexicons/source/watch/atmo/comment/listRecords.json b/src/lib/contrail/lexicons/source/watch/atmo/comment/listRecords.json new file mode 100644 index 0000000..1fe1372 --- /dev/null +++ b/src/lib/contrail/lexicons/source/watch/atmo/comment/listRecords.json @@ -0,0 +1,167 @@ +{ + "lexicon": 1, + "id": "watch.atmo.comment.listRecords", + "defs": { + "main": { + "type": "query", + "description": "Query social.popfeed.feed.comment records", + "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 an indexed DID or cached handle" + }, + "profiles": { + "type": "boolean", + "description": "Include indexed profile and identity information" + }, + "rootUri": { + "type": "string", + "description": "Filter by rootUri" + }, + "subjectType": { + "type": "string", + "description": "Filter by subjectType" + }, + "subjectUri": { + "type": "string", + "description": "Filter by subjectUri" + }, + "sort": { + "type": "string", + "knownValues": [ + "rootUri", + "subjectType", + "subjectUri" + ], + "description": "Field to sort by (default: time_us)" + }, + "order": { + "type": "string", + "knownValues": [ + "asc", + "desc" + ], + "description": "Sort direction" + } + } + }, + "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", + "cid", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "social.popfeed.feed.comment#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + } + } + }, + "profileEntry": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "union", + "refs": [ + "app.bsky.actor.profile#main", + "social.popfeed.actor.profile#main" + ] + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + } + } + } + } +} diff --git a/src/lib/contrail/lexicons/source/watch/atmo/getCursor.json b/src/lib/contrail/lexicons/source/watch/atmo/getCursor.json new file mode 100644 index 0000000..c8bf0f8 --- /dev/null +++ b/src/lib/contrail/lexicons/source/watch/atmo/getCursor.json @@ -0,0 +1,48 @@ +{ + "lexicon": 1, + "id": "watch.atmo.getCursor", + "defs": { + "main": { + "type": "query", + "description": "Get the committed primary ordered-source position", + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "properties": { + "position": { + "type": "ref", + "ref": "#sourcePosition" + }, + "updatedAt": { + "type": "integer" + }, + "updatedAtDate": { + "type": "string", + "format": "datetime" + } + } + } + } + }, + "sourcePosition": { + "type": "object", + "required": [ + "source", + "epoch", + "cursor" + ], + "properties": { + "source": { + "type": "string" + }, + "epoch": { + "type": "string" + }, + "cursor": { + "type": "string" + } + } + } + } +} diff --git a/src/lib/contrail/lexicons/source/watch/atmo/getProfile.json b/src/lib/contrail/lexicons/source/watch/atmo/getProfile.json new file mode 100644 index 0000000..081509e --- /dev/null +++ b/src/lib/contrail/lexicons/source/watch/atmo/getProfile.json @@ -0,0 +1,76 @@ +{ + "lexicon": 1, + "id": "watch.atmo.getProfile", + "defs": { + "main": { + "type": "query", + "parameters": { + "type": "params", + "required": [ + "actor" + ], + "properties": { + "actor": { + "type": "string", + "format": "at-identifier" + } + } + }, + "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" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "union", + "refs": [ + "app.bsky.actor.profile#main", + "social.popfeed.actor.profile#main" + ] + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + } + } + } + } +} diff --git a/src/lib/contrail/lexicons/source/watch/atmo/like/getRecord.json b/src/lib/contrail/lexicons/source/watch/atmo/like/getRecord.json new file mode 100644 index 0000000..6dd8311 --- /dev/null +++ b/src/lib/contrail/lexicons/source/watch/atmo/like/getRecord.json @@ -0,0 +1,113 @@ +{ + "lexicon": 1, + "id": "watch.atmo.like.getRecord", + "defs": { + "main": { + "type": "query", + "description": "Get a social.popfeed.feed.like 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 indexed profile and identity information" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "uri", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "social.popfeed.feed.like#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "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" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "union", + "refs": [ + "app.bsky.actor.profile#main", + "social.popfeed.actor.profile#main" + ] + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + } + } + } + } +} diff --git a/src/lib/contrail/lexicons/source/watch/atmo/like/listRecords.json b/src/lib/contrail/lexicons/source/watch/atmo/like/listRecords.json new file mode 100644 index 0000000..220b134 --- /dev/null +++ b/src/lib/contrail/lexicons/source/watch/atmo/like/listRecords.json @@ -0,0 +1,162 @@ +{ + "lexicon": 1, + "id": "watch.atmo.like.listRecords", + "defs": { + "main": { + "type": "query", + "description": "Query social.popfeed.feed.like records", + "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 an indexed DID or cached handle" + }, + "profiles": { + "type": "boolean", + "description": "Include indexed profile and identity information" + }, + "subjectType": { + "type": "string", + "description": "Filter by subjectType" + }, + "subjectUri": { + "type": "string", + "description": "Filter by subjectUri" + }, + "sort": { + "type": "string", + "knownValues": [ + "subjectType", + "subjectUri" + ], + "description": "Field to sort by (default: time_us)" + }, + "order": { + "type": "string", + "knownValues": [ + "asc", + "desc" + ], + "description": "Sort direction" + } + } + }, + "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", + "cid", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "social.popfeed.feed.like#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + } + } + }, + "profileEntry": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "union", + "refs": [ + "app.bsky.actor.profile#main", + "social.popfeed.actor.profile#main" + ] + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + } + } + } + } +} diff --git a/src/lib/contrail/lexicons/source/watch/atmo/list/getRecord.json b/src/lib/contrail/lexicons/source/watch/atmo/list/getRecord.json new file mode 100644 index 0000000..4b03196 --- /dev/null +++ b/src/lib/contrail/lexicons/source/watch/atmo/list/getRecord.json @@ -0,0 +1,281 @@ +{ + "lexicon": 1, + "id": "watch.atmo.list.getRecord", + "defs": { + "main": { + "type": "query", + "description": "Get a social.popfeed.feed.list 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 indexed profile and identity information" + }, + "hydrateComments": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "description": "Number of comments records to embed" + }, + "hydrateItems": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "description": "Number of items records to embed" + }, + "hydrateLikes": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "description": "Number of likes records to embed" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "uri", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "social.popfeed.feed.list#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + }, + "commentsCount": { + "type": "integer", + "description": "Total comments count" + }, + "comments": { + "type": "array", + "items": { + "type": "ref", + "ref": "#hydrateCommentsRecord" + } + }, + "itemsCount": { + "type": "integer", + "description": "Total items count" + }, + "items": { + "type": "array", + "items": { + "type": "ref", + "ref": "#hydrateItemsRecord" + } + }, + "likesCount": { + "type": "integer", + "description": "Total likes count" + }, + "likes": { + "type": "array", + "items": { + "type": "ref", + "ref": "#hydrateLikesRecord" + } + }, + "profiles": { + "type": "array", + "items": { + "type": "ref", + "ref": "#profileEntry" + } + } + } + } + } + }, + "hydrateCommentsRecord": { + "type": "object", + "required": [ + "uri", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "social.popfeed.feed.comment#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + } + } + }, + "hydrateItemsRecord": { + "type": "object", + "required": [ + "uri", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "social.popfeed.feed.listItem#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + } + } + }, + "hydrateLikesRecord": { + "type": "object", + "required": [ + "uri", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "social.popfeed.feed.like#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + } + } + }, + "profileEntry": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "union", + "refs": [ + "app.bsky.actor.profile#main", + "social.popfeed.actor.profile#main" + ] + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + } + } + } + } +} diff --git a/src/lib/contrail/lexicons/source/watch/atmo/list/listRecords.json b/src/lib/contrail/lexicons/source/watch/atmo/list/listRecords.json new file mode 100644 index 0000000..73d8953 --- /dev/null +++ b/src/lib/contrail/lexicons/source/watch/atmo/list/listRecords.json @@ -0,0 +1,349 @@ +{ + "lexicon": 1, + "id": "watch.atmo.list.listRecords", + "defs": { + "main": { + "type": "query", + "description": "Query social.popfeed.feed.list records", + "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 an indexed DID or cached handle" + }, + "profiles": { + "type": "boolean", + "description": "Include indexed profile and identity information" + }, + "createdAtMin": { + "type": "string", + "description": "Minimum value for createdAt" + }, + "createdAtMax": { + "type": "string", + "description": "Maximum value for createdAt" + }, + "listType": { + "type": "string", + "description": "Filter by listType" + }, + "commentsCountMin": { + "type": "integer", + "description": "Minimum total comments count" + }, + "hydrateComments": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "description": "Number of comments records to embed" + }, + "itemsCountMin": { + "type": "integer", + "description": "Minimum total items count" + }, + "hydrateItems": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "description": "Number of items records to embed" + }, + "likesCountMin": { + "type": "integer", + "description": "Minimum total likes count" + }, + "hydrateLikes": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "description": "Number of likes records to embed" + }, + "sort": { + "type": "string", + "knownValues": [ + "commentsCount", + "createdAt", + "itemsCount", + "likesCount", + "listType" + ], + "description": "Field to sort by (default: time_us)" + }, + "order": { + "type": "string", + "knownValues": [ + "asc", + "desc" + ], + "description": "Sort direction" + } + } + }, + "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", + "cid", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "social.popfeed.feed.list#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + }, + "commentsCount": { + "type": "integer", + "description": "Total comments count" + }, + "comments": { + "type": "array", + "items": { + "type": "ref", + "ref": "#hydrateCommentsRecord" + } + }, + "itemsCount": { + "type": "integer", + "description": "Total items count" + }, + "items": { + "type": "array", + "items": { + "type": "ref", + "ref": "#hydrateItemsRecord" + } + }, + "likesCount": { + "type": "integer", + "description": "Total likes count" + }, + "likes": { + "type": "array", + "items": { + "type": "ref", + "ref": "#hydrateLikesRecord" + } + } + } + }, + "hydrateCommentsRecord": { + "type": "object", + "required": [ + "uri", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "social.popfeed.feed.comment#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + } + } + }, + "hydrateItemsRecord": { + "type": "object", + "required": [ + "uri", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "social.popfeed.feed.listItem#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + } + } + }, + "hydrateLikesRecord": { + "type": "object", + "required": [ + "uri", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "social.popfeed.feed.like#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + } + } + }, + "profileEntry": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "union", + "refs": [ + "app.bsky.actor.profile#main", + "social.popfeed.actor.profile#main" + ] + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + } + } + } + } +} diff --git a/src/lib/contrail/lexicons/source/watch/atmo/listItem/getRecord.json b/src/lib/contrail/lexicons/source/watch/atmo/listItem/getRecord.json new file mode 100644 index 0000000..9e19cbf --- /dev/null +++ b/src/lib/contrail/lexicons/source/watch/atmo/listItem/getRecord.json @@ -0,0 +1,160 @@ +{ + "lexicon": 1, + "id": "watch.atmo.listItem.getRecord", + "defs": { + "main": { + "type": "query", + "description": "Get a social.popfeed.feed.listItem 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 indexed profile and identity information" + }, + "hydrateList": { + "type": "boolean", + "description": "Embed the referenced list record" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "uri", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "social.popfeed.feed.listItem#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + }, + "list": { + "type": "ref", + "ref": "#refListRecord" + }, + "profiles": { + "type": "array", + "items": { + "type": "ref", + "ref": "#profileEntry" + } + } + } + } + } + }, + "refListRecord": { + "type": "object", + "required": [ + "uri", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "social.popfeed.feed.list#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + } + } + }, + "profileEntry": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "union", + "refs": [ + "app.bsky.actor.profile#main", + "social.popfeed.actor.profile#main" + ] + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + } + } + } + } +} diff --git a/src/lib/contrail/lexicons/source/watch/atmo/listItem/listRecords.json b/src/lib/contrail/lexicons/source/watch/atmo/listItem/listRecords.json new file mode 100644 index 0000000..dd894fd --- /dev/null +++ b/src/lib/contrail/lexicons/source/watch/atmo/listItem/listRecords.json @@ -0,0 +1,238 @@ +{ + "lexicon": 1, + "id": "watch.atmo.listItem.listRecords", + "defs": { + "main": { + "type": "query", + "description": "Query social.popfeed.feed.listItem records", + "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 an indexed DID or cached handle" + }, + "profiles": { + "type": "boolean", + "description": "Include indexed profile and identity information" + }, + "addedAtMin": { + "type": "string", + "description": "Minimum value for addedAt" + }, + "addedAtMax": { + "type": "string", + "description": "Maximum value for addedAt" + }, + "creativeWorkType": { + "type": "string", + "description": "Filter by creativeWorkType" + }, + "identifiersTmdbId": { + "type": "string", + "description": "Filter by identifiers.tmdbId" + }, + "identifiersTmdbTvSeriesId": { + "type": "string", + "description": "Filter by identifiers.tmdbTvSeriesId" + }, + "listType": { + "type": "string", + "description": "Filter by listType" + }, + "listUri": { + "type": "string", + "description": "Filter by listUri" + }, + "status": { + "type": "string", + "description": "Filter by status" + }, + "hydrateList": { + "type": "boolean", + "description": "Embed the referenced list record" + }, + "sort": { + "type": "string", + "knownValues": [ + "addedAt", + "creativeWorkType", + "identifiersTmdbId", + "identifiersTmdbTvSeriesId", + "listType", + "listUri", + "status" + ], + "description": "Field to sort by (default: time_us)" + }, + "order": { + "type": "string", + "knownValues": [ + "asc", + "desc" + ], + "description": "Sort direction" + } + } + }, + "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", + "cid", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "social.popfeed.feed.listItem#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + }, + "list": { + "type": "ref", + "ref": "#refListRecord" + } + } + }, + "refListRecord": { + "type": "object", + "required": [ + "uri", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "social.popfeed.feed.list#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + } + } + }, + "profileEntry": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "union", + "refs": [ + "app.bsky.actor.profile#main", + "social.popfeed.actor.profile#main" + ] + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + } + } + } + } +} diff --git a/src/lib/contrail/lexicons/source/watch/atmo/notifyOfUpdate.json b/src/lib/contrail/lexicons/source/watch/atmo/notifyOfUpdate.json new file mode 100644 index 0000000..3b0d44d --- /dev/null +++ b/src/lib/contrail/lexicons/source/watch/atmo/notifyOfUpdate.json @@ -0,0 +1,56 @@ +{ + "lexicon": 1, + "id": "watch.atmo.notifyOfUpdate", + "defs": { + "main": { + "type": "procedure", + "description": "Fetch changed records from their authoritative PDS for immediate indexing", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "uris": { + "type": "array", + "items": { + "type": "string", + "format": "at-uri" + }, + "maxLength": 25 + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "indexed", + "deleted" + ], + "properties": { + "indexed": { + "type": "integer", + "minimum": 0 + }, + "deleted": { + "type": "integer", + "minimum": 0 + }, + "errors": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } +} diff --git a/src/lib/contrail/lexicons/source/watch/atmo/review/getRecord.json b/src/lib/contrail/lexicons/source/watch/atmo/review/getRecord.json new file mode 100644 index 0000000..05f60d5 --- /dev/null +++ b/src/lib/contrail/lexicons/source/watch/atmo/review/getRecord.json @@ -0,0 +1,225 @@ +{ + "lexicon": 1, + "id": "watch.atmo.review.getRecord", + "defs": { + "main": { + "type": "query", + "description": "Get a social.popfeed.feed.review 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 indexed profile and identity information" + }, + "hydrateComments": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "description": "Number of comments records to embed" + }, + "hydrateLikes": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "description": "Number of likes records to embed" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "uri", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "social.popfeed.feed.review#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + }, + "commentsCount": { + "type": "integer", + "description": "Total comments count" + }, + "comments": { + "type": "array", + "items": { + "type": "ref", + "ref": "#hydrateCommentsRecord" + } + }, + "likesCount": { + "type": "integer", + "description": "Total likes count" + }, + "likes": { + "type": "array", + "items": { + "type": "ref", + "ref": "#hydrateLikesRecord" + } + }, + "profiles": { + "type": "array", + "items": { + "type": "ref", + "ref": "#profileEntry" + } + } + } + } + } + }, + "hydrateCommentsRecord": { + "type": "object", + "required": [ + "uri", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "social.popfeed.feed.comment#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + } + } + }, + "hydrateLikesRecord": { + "type": "object", + "required": [ + "uri", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "social.popfeed.feed.like#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + } + } + }, + "profileEntry": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "union", + "refs": [ + "app.bsky.actor.profile#main", + "social.popfeed.actor.profile#main" + ] + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + } + } + } + } +} diff --git a/src/lib/contrail/lexicons/source/watch/atmo/review/listRecords.json b/src/lib/contrail/lexicons/source/watch/atmo/review/listRecords.json new file mode 100644 index 0000000..c1d7ac8 --- /dev/null +++ b/src/lib/contrail/lexicons/source/watch/atmo/review/listRecords.json @@ -0,0 +1,284 @@ +{ + "lexicon": 1, + "id": "watch.atmo.review.listRecords", + "defs": { + "main": { + "type": "query", + "description": "Query social.popfeed.feed.review records", + "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 an indexed DID or cached handle" + }, + "profiles": { + "type": "boolean", + "description": "Include indexed profile and identity information" + }, + "creativeWorkType": { + "type": "string", + "description": "Filter by creativeWorkType" + }, + "identifiersTmdbId": { + "type": "string", + "description": "Filter by identifiers.tmdbId" + }, + "commentsCountMin": { + "type": "integer", + "description": "Minimum total comments count" + }, + "hydrateComments": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "description": "Number of comments records to embed" + }, + "likesCountMin": { + "type": "integer", + "description": "Minimum total likes count" + }, + "hydrateLikes": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "description": "Number of likes records to embed" + }, + "sort": { + "type": "string", + "knownValues": [ + "commentsCount", + "creativeWorkType", + "identifiersTmdbId", + "likesCount" + ], + "description": "Field to sort by (default: time_us)" + }, + "order": { + "type": "string", + "knownValues": [ + "asc", + "desc" + ], + "description": "Sort direction" + } + } + }, + "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", + "cid", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "social.popfeed.feed.review#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + }, + "commentsCount": { + "type": "integer", + "description": "Total comments count" + }, + "comments": { + "type": "array", + "items": { + "type": "ref", + "ref": "#hydrateCommentsRecord" + } + }, + "likesCount": { + "type": "integer", + "description": "Total likes count" + }, + "likes": { + "type": "array", + "items": { + "type": "ref", + "ref": "#hydrateLikesRecord" + } + } + } + }, + "hydrateCommentsRecord": { + "type": "object", + "required": [ + "uri", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "social.popfeed.feed.comment#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + } + } + }, + "hydrateLikesRecord": { + "type": "object", + "required": [ + "uri", + "value", + "did", + "collection", + "rkey", + "time_us" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "ref", + "ref": "social.popfeed.feed.like#main" + }, + "did": { + "type": "string", + "format": "did" + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + }, + "time_us": { + "type": "integer" + } + } + }, + "profileEntry": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "value": { + "type": "union", + "refs": [ + "app.bsky.actor.profile#main", + "social.popfeed.actor.profile#main" + ] + }, + "collection": { + "type": "string", + "format": "nsid" + }, + "rkey": { + "type": "string" + } + } + } + } +} diff --git a/src/lib/contrail/lexicons/source/watch/atmo/review/listWrittenRecords.json b/src/lib/contrail/lexicons/source/watch/atmo/review/listWrittenRecords.json new file mode 100644 index 0000000..39840d2 --- /dev/null +++ b/src/lib/contrail/lexicons/source/watch/atmo/review/listWrittenRecords.json @@ -0,0 +1,70 @@ +{ + "lexicon": 1, + "id": "watch.atmo.review.listWrittenRecords", + "defs": { + "main": { + "type": "query", + "description": "Query social.popfeed.feed.review records with non-empty text", + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 200, + "default": 50 + }, + "cursor": { + "type": "string" + }, + "profiles": { + "type": "boolean", + "description": "Include indexed profile and identity information" + }, + "hydrateLikes": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "description": "Number of likes records to embed" + }, + "order": { + "type": "string", + "knownValues": [ + "asc", + "desc" + ], + "description": "Sort direction" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [ + "records" + ], + "properties": { + "records": { + "type": "array", + "items": { + "type": "ref", + "ref": "watch.atmo.review.listRecords#record" + } + }, + "cursor": { + "type": "string" + }, + "profiles": { + "type": "array", + "items": { + "type": "ref", + "ref": "watch.atmo.review.listRecords#profileEntry" + } + } + } + } + } + } + } +} diff --git a/src/lib/list-write.remote.ts b/src/lib/list-write.remote.ts index dc6b9b8..7a15762 100644 --- a/src/lib/list-write.remote.ts +++ b/src/lib/list-write.remote.ts @@ -5,7 +5,7 @@ import type { Client } from '@atcute/client'; import { isCanonicalResourceUri, parseCanonicalResourceUri } from '@atcute/lexicons'; import type { CanonicalResourceUri, Did, Nsid } from '@atcute/lexicons'; import * as v from 'valibot'; -import { contrail } from '$lib/contrail-active'; +import { contrail } from '$lib/contrail-client.server'; import type { Main as ListRecord } from '$lib/contrail/types/types/social/popfeed/feed/list'; import type { Main as ListItemRecord } from '$lib/contrail/types/types/social/popfeed/feed/listItem'; import { backdropUrl } from '$lib/images'; diff --git a/src/lib/lists.server.ts b/src/lib/lists.server.ts index 0b0cd7b..0d970db 100644 --- a/src/lib/lists.server.ts +++ b/src/lib/lists.server.ts @@ -1,5 +1,5 @@ import { getAtprotoCdnImageUrl } from '$lib/atproto/images'; -import { contrail } from '$lib/contrail-active'; +import { contrail } from '$lib/contrail-client.server'; import type * as ListRecords from '$lib/contrail/types/types/watch/atmo/list/listRecords'; import type * as ListItemRecords from '$lib/contrail/types/types/watch/atmo/listItem/listRecords'; import type { ActorSummary, MediaImage, MediaListModel, MediaSummary } from '$lib/types'; diff --git a/src/lib/review-interactions.remote.ts b/src/lib/review-interactions.remote.ts index e49595b..1f4bb6d 100644 --- a/src/lib/review-interactions.remote.ts +++ b/src/lib/review-interactions.remote.ts @@ -4,7 +4,7 @@ import { isCanonicalResourceUri, parseCanonicalResourceUri } from '@atcute/lexic import type { CanonicalResourceUri, Did } from '@atcute/lexicons'; import { createTID } from '@svelte-atproto/oauth/helper'; import * as v from 'valibot'; -import { contrail } from '$lib/contrail-active'; +import { contrail } from '$lib/contrail-client.server'; const REVIEW_COLLECTION = 'social.popfeed.feed.review'; const LIKE_COLLECTION = 'social.popfeed.feed.like'; diff --git a/src/lib/review-write.remote.ts b/src/lib/review-write.remote.ts index 29dcb87..91e3794 100644 --- a/src/lib/review-write.remote.ts +++ b/src/lib/review-write.remote.ts @@ -5,7 +5,7 @@ import type { Client } from '@atcute/client'; import { isCanonicalResourceUri, parseCanonicalResourceUri } from '@atcute/lexicons'; import type { CanonicalResourceUri, Did } from '@atcute/lexicons'; import * as v from 'valibot'; -import { contrail } from '$lib/contrail-active'; +import { contrail } from '$lib/contrail-client.server'; import type { Main as ListRecord } from '$lib/contrail/types/types/social/popfeed/feed/list'; import type { Main as ListItemRecord } from '$lib/contrail/types/types/social/popfeed/feed/listItem'; import type { Main as ReviewRecord } from '$lib/contrail/types/types/social/popfeed/feed/review'; diff --git a/src/lib/reviews.server.ts b/src/lib/reviews.server.ts index c28ad6c..f3db340 100644 --- a/src/lib/reviews.server.ts +++ b/src/lib/reviews.server.ts @@ -1,6 +1,6 @@ import { isCanonicalResourceUri, parseCanonicalResourceUri, type Did } from '@atcute/lexicons'; import { getAtprotoCdnImageUrl } from '$lib/atproto/images'; -import { contrail, contrailMethods } from '$lib/contrail-active'; +import { contrail, contrailMethods } from '$lib/contrail-client.server'; import type * as CommentListRecords from '$lib/contrail/types/types/watch/atmo/comment/listRecords'; import type * as LikeListRecords from '$lib/contrail/types/types/watch/atmo/like/listRecords'; import type * as ReviewListRecords from '$lib/contrail/types/types/watch/atmo/review/listRecords'; diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts index a37c2d3..ca30e2c 100644 --- a/src/routes/+layout.server.ts +++ b/src/routes/+layout.server.ts @@ -1,6 +1,6 @@ import type { Did } from '@atcute/lexicons'; import { getAtprotoCdnImageUrl } from '$lib/atproto/images'; -import { contrail } from '$lib/contrail-active'; +import { contrail } from '$lib/contrail-client.server'; import type { LayoutServerLoad } from './$types'; async function getViewerAvatar(did: Did) { diff --git a/src/routes/profile/[actor]/+layout.server.ts b/src/routes/profile/[actor]/+layout.server.ts index 66af07b..d42d74b 100644 --- a/src/routes/profile/[actor]/+layout.server.ts +++ b/src/routes/profile/[actor]/+layout.server.ts @@ -1,5 +1,5 @@ import { getAtprotoCdnImageUrl } from '$lib/atproto/images'; -import { contrail } from '$lib/contrail-active'; +import { contrail } from '$lib/contrail-client.server'; import { isActorIdentifier } from '@atcute/lexicons/syntax'; import { error } from '@sveltejs/kit'; import type { LayoutServerLoad } from './$types'; diff --git a/src/routes/profile/[actor]/+page.server.ts b/src/routes/profile/[actor]/+page.server.ts index 5adf01a..0dfa68a 100644 --- a/src/routes/profile/[actor]/+page.server.ts +++ b/src/routes/profile/[actor]/+page.server.ts @@ -1,5 +1,5 @@ import { error } from '@sveltejs/kit'; -import { contrail } from '$lib/contrail-active'; +import { contrail } from '$lib/contrail-client.server'; import { getProfileMediaLists } from '$lib/lists.server'; import { getViewerReviewLikes, toReview } from '$lib/reviews.server'; import type { PageServerLoad } from './$types'; diff --git a/src/routes/profile/[actor]/review/[rkey]/+page.server.ts b/src/routes/profile/[actor]/review/[rkey]/+page.server.ts index dadd278..8d327af 100644 --- a/src/routes/profile/[actor]/review/[rkey]/+page.server.ts +++ b/src/routes/profile/[actor]/review/[rkey]/+page.server.ts @@ -1,7 +1,7 @@ import { error } from '@sveltejs/kit'; import type { ResourceUri } from '@atcute/lexicons'; import { isRecordKey } from '@atcute/lexicons/syntax'; -import { contrail } from '$lib/contrail-active'; +import { contrail } from '$lib/contrail-client.server'; import { getReviewInteractions, toReview } from '$lib/reviews.server'; import type { PageServerLoad } from './$types';