A music player that connects to your cloud/distributed storage.

feat: support large sets of atproto records

+77 -17
+1 -1
.env
··· 1 - ATPROTO_CLIENT_ID=https://measures-cancel-archive-www.trycloudflare.com/oauth-client-metadata.tunnel.json 1 + ATPROTO_CLIENT_ID=https://handed-pixels-solo-folks.trycloudflare.com/oauth-client-metadata.tunnel.json 2 2 DISABLE_AUTOMATIC_TRACKS_PROCESSING=t
+30 -6
src/components/output/raw/atproto/element.js
··· 1 1 import { Client, ClientResponseError, ok } from "@atcute/client"; 2 + import * as TID from "@atcute/tid"; 2 3 3 4 import { computed, signal } from "@common/signal.js"; 4 5 import { BroadcastedOutputElement, outputManager } from "../../common.js"; ··· 13 14 } from "./oauth.js"; 14 15 15 16 /** 17 + * @import {TrackBundle} from "@definitions/types.d.ts" 16 18 * @import {OutputManager} from "../../types.d.ts" 17 19 * @import {ATProtoOutputElement} from "./types.d.ts" 18 20 */ ··· 60 62 }, 61 63 tracks: { 62 64 empty: () => [], 63 - get: () => this.listRecords("sh.diffuse.output.track"), 64 - put: (data) => this.#putRecords("sh.diffuse.output.track", data), 65 + get: async () => { 66 + const bundles = await this.listRecords( 67 + "sh.diffuse.output.trackBundle", 68 + ); 69 + 70 + return bundles.flatMap((bundle) => bundle.tracks ?? []); 71 + }, 72 + put: (data) => { 73 + /** @type {TrackBundle[]} */ 74 + const bundles = []; 75 + 76 + for (let i = 0; i < data.length; i += 100) { 77 + bundles.push({ 78 + $type: "sh.diffuse.output.trackBundle", 79 + id: TID.now(), 80 + tracks: data.slice(i, i + 100), 81 + }); 82 + } 83 + 84 + return this.#putRecords("sh.diffuse.output.trackBundle", bundles, { 85 + batchSize: 1, 86 + }); 87 + }, 65 88 }, 66 89 }); 67 90 ··· 283 306 /** 284 307 * @param {string} collection 285 308 * @param {Array<{ id: string }>} data 309 + * @param {{ batchSize?: number }} [options] 286 310 */ 287 - async #putRecords(collection, data) { 311 + async #putRecords(collection, data, { batchSize = 100 } = {}) { 288 312 const rpc = this.#rpc; 289 313 if (!rpc || !this.#did.value) return; 290 314 ··· 351 375 } 352 376 } 353 377 354 - // 4. Apply in batches of 100 355 - for (let i = 0; i < writes.length; i += 100) { 356 - const batch = writes.slice(i, i + 100); 378 + // 4. Apply in batches 379 + for (let i = 0; i < writes.length; i += batchSize) { 380 + const batch = writes.slice(i, i + batchSize); 357 381 358 382 const result = await ok(rpc.post("com.atproto.repo.applyWrites", { 359 383 input: { repo: this.#did.value, writes: batch },
+2 -1
src/components/transformer/output/raw/atproto-sync/element.js
··· 190 190 191 191 this.#storeRev(atproto.rev()); 192 192 this.#clearDirty(); 193 - } finally { 193 + } catch (err) { 194 + console.warn("Sync failed:", err); 194 195 this.#syncing = false; 195 196 } 196 197 }
+1
src/definitions/index.ts
··· 3 3 export * as ShDiffuseOutputPlaylistItem from "./types/sh/diffuse/output/playlistItem.ts"; 4 4 export * as ShDiffuseOutputTheme from "./types/sh/diffuse/output/theme.ts"; 5 5 export * as ShDiffuseOutputTrack from "./types/sh/diffuse/output/track.ts"; 6 + export * as ShDiffuseOutputTrackBundle from "./types/sh/diffuse/output/trackBundle.ts";
+23
src/definitions/output/trackBundle.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.diffuse.output.trackBundle", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "record": { 8 + "type": "object", 9 + "required": ["id", "tracks"], 10 + "properties": { 11 + "id": { "type": "string" }, 12 + "createdAt": { "type": "string", "format": "datetime" }, 13 + "tracks": { 14 + "type": "array", 15 + "description": "A bundle of tracks", 16 + "items": { "type": "ref", "ref": "sh.diffuse.output.track" } 17 + }, 18 + "updatedAt": { "type": "string", "format": "datetime" } 19 + } 20 + } 21 + } 22 + } 23 + }
+4
src/definitions/types.d.ts
··· 13 13 Stats as TrackStats, 14 14 Tags as TrackTags, 15 15 } from "./types/sh/diffuse/output/track.ts"; 16 + 17 + export type { 18 + Main as TrackBundle, 19 + } from "./types/sh/diffuse/output/trackBundle.ts";
+4
src/index.vto
··· 169 169 desc: > 170 170 Represents audio that can be played, or a placeholder for a source of tracks. Contains a URI that will resolve to the audio. 171 171 url: "definitions/output/track.json" 172 + - title: "Output / Track Bundle" 173 + desc: > 174 + A bundle of tracks. 175 + url: "definitions/output/trackBundle.json" 172 176 173 177 --- 174 178
+1 -1
src/oauth-client-metadata.json
··· 3 3 "client_name": "Diffuse", 4 4 "client_uri": "https://elements.diffuse.sh", 5 5 "redirect_uris": ["https://elements.diffuse.sh/oauth/callback"], 6 - "scope": "atproto repo?collection=sh.diffuse.output.facet&collection=sh.diffuse.output.playlistItem&collection=sh.diffuse.output.theme&collection=sh.diffuse.output.track", 6 + "scope": "atproto repo?collection=sh.diffuse.output.facet&collection=sh.diffuse.output.playlistItem&collection=sh.diffuse.output.theme&collection=sh.diffuse.output.track&collection=sh.diffuse.output.trackBundle", 7 7 "grant_types": ["authorization_code", "refresh_token"], 8 8 "response_types": ["code"], 9 9 "token_endpoint_auth_method": "none",
+4 -4
src/oauth-client-metadata.tunnel.json
··· 1 1 { 2 - "client_id": "https://measures-cancel-archive-www.trycloudflare.com/oauth-client-metadata.tunnel.json", 2 + "client_id": "https://handed-pixels-solo-folks.trycloudflare.com/oauth-client-metadata.tunnel.json", 3 3 "client_name": "Diffuse", 4 - "client_uri": "https://measures-cancel-archive-www.trycloudflare.com", 5 - "redirect_uris": ["https://measures-cancel-archive-www.trycloudflare.com/oauth/callback"], 6 - "scope": "atproto repo?collection=sh.diffuse.output.facet&collection=sh.diffuse.output.playlistItem&collection=sh.diffuse.output.theme&collection=sh.diffuse.output.track", 4 + "client_uri": "https://handed-pixels-solo-folks.trycloudflare.com", 5 + "redirect_uris": ["https://handed-pixels-solo-folks.trycloudflare.com/oauth/callback"], 6 + "scope": "atproto repo?collection=sh.diffuse.output.facet&collection=sh.diffuse.output.playlistItem&collection=sh.diffuse.output.theme&collection=sh.diffuse.output.track&collection=sh.diffuse.output.trackBundle", 7 7 "grant_types": ["authorization_code", "refresh_token"], 8 8 "response_types": ["code"], 9 9 "token_endpoint_auth_method": "none",
+7 -4
tasks/replace-gen-import-extensions.ts
··· 1 1 import { readTextFileSync } from "@std/fs/unstable-read-text-file"; 2 2 import { writeTextFileSync } from "@std/fs/unstable-write-text-file"; 3 3 4 - const PATH = "./src/definitions/index.ts"; 4 + function replace(path: string) { 5 + const text = readTextFileSync(path); 6 + const withTsImports = text.replaceAll(/\.js";/g, '.ts";'); 5 7 6 - const text = readTextFileSync(PATH); 7 - const withTsImports = text.replaceAll(/\.js";/g, '.ts";'); 8 + writeTextFileSync(path, withTsImports); 9 + } 8 10 9 - writeTextFileSync(PATH, withTsImports); 11 + replace("./src/definitions/index.ts"); 12 + replace("./src/definitions/types/sh/diffuse/output/trackBundle.ts");