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

Configure Feed

Select the types of activity you want to include in your feed.

feat: add experimental s3 output

+220
+1
src/components/output/bytes/s3/constants.js
··· 1 + export const OBJECT_PREFIX = "diffuse/output/";
+160
src/components/output/bytes/s3/element.js
··· 1 + import { BroadcastableDiffuseElement } from "@common/element.js"; 2 + import { outputManager } from "../../common.js"; 3 + 4 + /** 5 + * @import {ProxiedActions} from "@common/worker.d.ts" 6 + * @import {OutputElement, OutputManager} from "../../types.d.ts" 7 + * @import {Bucket} from "@components/input/s3/types.d.ts" 8 + * @import {S3OutputWorkerActions} from "./types.d.ts" 9 + */ 10 + 11 + //////////////////////////////////////////// 12 + // ELEMENT 13 + //////////////////////////////////////////// 14 + 15 + /** 16 + * @implements {OutputElement<Uint8Array | undefined>} 17 + */ 18 + class S3Output extends BroadcastableDiffuseElement { 19 + static NAME = "diffuse/output/polymorphic/s3"; 20 + static WORKER_URL = "components/output/polymorphic/s3/worker.js"; 21 + 22 + #manager; 23 + 24 + constructor() { 25 + super(); 26 + 27 + /** @type {ProxiedActions<S3OutputWorkerActions>} */ 28 + this.proxy = this.workerProxy(); 29 + 30 + /** @type {OutputManager<Uint8Array | undefined>} */ 31 + this.#manager = outputManager({ 32 + facets: { 33 + empty: () => undefined, 34 + get: () => this.#get("facets"), 35 + put: (data) => this.#put("facets", data), 36 + }, 37 + init: () => this.whenConnected(), 38 + playlists: { 39 + empty: () => undefined, 40 + get: () => this.#get("playlists"), 41 + put: (data) => this.#put("playlists", data), 42 + }, 43 + themes: { 44 + empty: () => undefined, 45 + get: () => this.#get("themes"), 46 + put: (data) => this.#put("themes", data), 47 + }, 48 + tracks: { 49 + empty: () => undefined, 50 + get: () => this.#get("tracks"), 51 + put: (data) => this.#put("tracks", data), 52 + }, 53 + }); 54 + 55 + this.facets = this.#manager.facets; 56 + this.playlists = this.#manager.playlists; 57 + this.themes = this.#manager.themes; 58 + this.tracks = this.#manager.tracks; 59 + } 60 + 61 + // LIFECYCLE 62 + 63 + /** 64 + * @override 65 + */ 66 + connectedCallback() { 67 + // Broadcast if needed 68 + if (this.hasAttribute("group")) { 69 + const actions = this.broadcast(this.nameWithGroup, { 70 + put: { strategy: "replicate", fn: this.#putIncoming }, 71 + }); 72 + 73 + if (actions) { 74 + this.#put = this.#putOutgoing(actions.put); 75 + } 76 + } 77 + 78 + // Super 79 + super.connectedCallback(); 80 + } 81 + 82 + // BUCKET 83 + 84 + /** 85 + * @param {Bucket} bucket 86 + */ 87 + setBucket(bucket) { 88 + this.#bucketValue = bucket; 89 + } 90 + 91 + /** @type {Bucket | undefined} */ 92 + #bucketValue; 93 + 94 + /** @returns {Bucket} */ 95 + #bucket() { 96 + if (!this.#bucketValue) { 97 + throw new Error("Bucket not set, call setBucket() first."); 98 + } 99 + return this.#bucketValue; 100 + } 101 + 102 + // GET & PUT 103 + 104 + /** @param {string} name */ 105 + #getProxy = (name) => 106 + this.proxy.get({ bucket: this.#bucket(), name: this.#cat(name) }); 107 + #get = this.#getProxy; 108 + 109 + /** @param {string} name; @param {any} data */ 110 + #putProxy = (name, data) => 111 + this.proxy.put({ bucket: this.#bucket(), data, name: this.#cat(name) }); 112 + #put = this.#putProxy; 113 + 114 + /** 115 + * @param {(uuidSender: ReturnType<typeof crypto.randomUUID>, name: string, data: any) => Promise<void>} action 116 + * @returns {(name: string, data: any) => Promise<void>} 117 + */ 118 + #putOutgoing = (action) => async (name, data) => { 119 + return await action(this.uuid, name, data); 120 + }; 121 + 122 + /** 123 + * @param {ReturnType<typeof crypto.randomUUID>} uuidSender 124 + * @param {string} name 125 + * @param {any} data 126 + */ 127 + #putIncoming(uuidSender, name, data) { 128 + if (uuidSender === this.uuid) { 129 + // Initiator 130 + this.#putProxy(name, data); 131 + } else { 132 + // Listener 133 + if (name === "facets") this.#manager.signals.facets.value = data; 134 + if (name === "playlists") this.#manager.signals.playlists.value = data; 135 + if (name === "themes") this.#manager.signals.themes.value = data; 136 + if (name === "tracks") this.#manager.signals.tracks.value = data; 137 + } 138 + } 139 + 140 + // 🛠️ 141 + 142 + /** @param {string} name */ 143 + #cat(name) { 144 + const namespace = this.hasAttribute("namespace") 145 + ? this.getAttribute("namespace") + "/" 146 + : ""; 147 + return `${namespace}${name}`; 148 + } 149 + } 150 + 151 + export default S3Output; 152 + 153 + //////////////////////////////////////////// 154 + // REGISTER 155 + //////////////////////////////////////////// 156 + 157 + export const CLASS = S3Output; 158 + export const NAME = "dop-s3"; 159 + 160 + customElements.define(NAME, S3Output);
+10
src/components/output/bytes/s3/types.d.ts
··· 1 + import type { Bucket } from "@components/input/s3/types.d.ts"; 2 + 3 + export type S3OutputWorkerActions = { 4 + get(args: { bucket: Bucket; name: string }): Promise<Uint8Array | undefined>; 5 + put(args: { 6 + bucket: Bucket; 7 + data: Uint8Array; 8 + name: string; 9 + }): Promise<void>; 10 + };
+49
src/components/output/bytes/s3/worker.js
··· 1 + import { createClient } from "@components/input/s3/common.js"; 2 + import { ostiary, rpc } from "@common/worker.js"; 3 + 4 + import { OBJECT_PREFIX } from "./constants.js"; 5 + 6 + /** 7 + * @import {S3OutputWorkerActions} from "./types.d.ts" 8 + */ 9 + 10 + //////////////////////////////////////////// 11 + // ACTIONS 12 + //////////////////////////////////////////// 13 + 14 + /** 15 + * @type {S3OutputWorkerActions["get"]} 16 + */ 17 + export async function get({ bucket, name }) { 18 + const client = createClient(bucket); 19 + const key = `${OBJECT_PREFIX}${name}`; 20 + 21 + try { 22 + const response = await client.getObject(key); 23 + return await response.bytes(); 24 + } catch (err) { 25 + // Object doesn't exist yet, return undefined 26 + return undefined; 27 + } 28 + } 29 + 30 + /** 31 + * @type {S3OutputWorkerActions["put"]} 32 + */ 33 + export async function put({ bucket, data, name }) { 34 + const client = createClient(bucket); 35 + const key = `${OBJECT_PREFIX}${name}`; 36 + 37 + await client.putObject(key, data); 38 + } 39 + 40 + //////////////////////////////////////////// 41 + // ⚡️ 42 + //////////////////////////////////////////// 43 + 44 + ostiary((context) => { 45 + rpc(context, { 46 + get, 47 + put, 48 + }); 49 + });