@rocksky/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +279 -0
  3. package/dist/client.d.ts +107 -0
  4. package/dist/client.d.ts.map +1 -0
  5. package/dist/errors.d.ts +26 -0
  6. package/dist/errors.d.ts.map +1 -0
  7. package/dist/http.d.ts +28 -0
  8. package/dist/http.d.ts.map +1 -0
  9. package/dist/index.d.ts +18 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +1492 -0
  12. package/dist/namespaces/_helpers.d.ts +9 -0
  13. package/dist/namespaces/_helpers.d.ts.map +1 -0
  14. package/dist/namespaces/actor.d.ts +30 -0
  15. package/dist/namespaces/actor.d.ts.map +1 -0
  16. package/dist/namespaces/album.d.ts +18 -0
  17. package/dist/namespaces/album.d.ts.map +1 -0
  18. package/dist/namespaces/apikey.d.ts +27 -0
  19. package/dist/namespaces/apikey.d.ts.map +1 -0
  20. package/dist/namespaces/artist.d.ts +31 -0
  21. package/dist/namespaces/artist.d.ts.map +1 -0
  22. package/dist/namespaces/charts.d.ts +25 -0
  23. package/dist/namespaces/charts.d.ts.map +1 -0
  24. package/dist/namespaces/dropbox.d.ts +19 -0
  25. package/dist/namespaces/dropbox.d.ts.map +1 -0
  26. package/dist/namespaces/feed.d.ts +38 -0
  27. package/dist/namespaces/feed.d.ts.map +1 -0
  28. package/dist/namespaces/googledrive.d.ts +16 -0
  29. package/dist/namespaces/googledrive.d.ts.map +1 -0
  30. package/dist/namespaces/graph.d.ts +28 -0
  31. package/dist/namespaces/graph.d.ts.map +1 -0
  32. package/dist/namespaces/like.d.ts +14 -0
  33. package/dist/namespaces/like.d.ts.map +1 -0
  34. package/dist/namespaces/mirror.d.ts +15 -0
  35. package/dist/namespaces/mirror.d.ts.map +1 -0
  36. package/dist/namespaces/player.d.ts +41 -0
  37. package/dist/namespaces/player.d.ts.map +1 -0
  38. package/dist/namespaces/playlist.d.ts +40 -0
  39. package/dist/namespaces/playlist.d.ts.map +1 -0
  40. package/dist/namespaces/scrobble.d.ts +43 -0
  41. package/dist/namespaces/scrobble.d.ts.map +1 -0
  42. package/dist/namespaces/shout.d.ts +38 -0
  43. package/dist/namespaces/shout.d.ts.map +1 -0
  44. package/dist/namespaces/song.d.ts +48 -0
  45. package/dist/namespaces/song.d.ts.map +1 -0
  46. package/dist/namespaces/spotify.d.ts +17 -0
  47. package/dist/namespaces/spotify.d.ts.map +1 -0
  48. package/dist/namespaces/stats.d.ts +14 -0
  49. package/dist/namespaces/stats.d.ts.map +1 -0
  50. package/dist/paginate.d.ts +38 -0
  51. package/dist/paginate.d.ts.map +1 -0
  52. package/dist/pipe.d.ts +36 -0
  53. package/dist/pipe.d.ts.map +1 -0
  54. package/dist/realtime.d.ts +144 -0
  55. package/dist/realtime.d.ts.map +1 -0
  56. package/dist/types.d.ts +29 -0
  57. package/dist/types.d.ts.map +1 -0
  58. package/package.json +65 -0
  59. package/src/client.ts +235 -0
  60. package/src/errors.ts +47 -0
  61. package/src/http.ts +195 -0
  62. package/src/index.ts +85 -0
  63. package/src/namespaces/_helpers.ts +17 -0
  64. package/src/namespaces/actor.ts +89 -0
  65. package/src/namespaces/album.ts +34 -0
  66. package/src/namespaces/apikey.ts +50 -0
  67. package/src/namespaces/artist.ts +74 -0
  68. package/src/namespaces/charts.ts +47 -0
  69. package/src/namespaces/dropbox.ts +47 -0
  70. package/src/namespaces/feed.ts +106 -0
  71. package/src/namespaces/googledrive.ts +36 -0
  72. package/src/namespaces/graph.ts +63 -0
  73. package/src/namespaces/like.ts +40 -0
  74. package/src/namespaces/mirror.ts +31 -0
  75. package/src/namespaces/player.ts +127 -0
  76. package/src/namespaces/playlist.ts +85 -0
  77. package/src/namespaces/scrobble.ts +67 -0
  78. package/src/namespaces/shout.ts +90 -0
  79. package/src/namespaces/song.ts +81 -0
  80. package/src/namespaces/spotify.ts +52 -0
  81. package/src/namespaces/stats.ts +23 -0
  82. package/src/paginate.ts +90 -0
  83. package/src/pipe.ts +146 -0
  84. package/src/realtime.ts +408 -0
  85. package/src/types.ts +41 -0
@@ -0,0 +1,38 @@
1
+ export type PageOpts = {
2
+ limit: number;
3
+ offset: number;
4
+ cursor?: string;
5
+ };
6
+ export type PageResult<T> = {
7
+ items: T[];
8
+ cursor?: string;
9
+ };
10
+ export type PaginateArgs<T> = {
11
+ /**
12
+ * Fetch one page. Return either:
13
+ * - an array of items (offset/limit mode — pagination stops when an empty
14
+ * page or a short page comes back), or
15
+ * - a `{ items, cursor }` object (cursor mode — pagination stops when
16
+ * `cursor` is missing/null).
17
+ */
18
+ fetch: (opts: PageOpts) => Promise<PageResult<T> | T[]>;
19
+ pageSize?: number;
20
+ maxItems?: number;
21
+ signal?: AbortSignal;
22
+ };
23
+ /**
24
+ * Page through an XRPC endpoint as an async iterable.
25
+ *
26
+ * for await (const s of paginate({
27
+ * fetch: ({ limit, offset }) =>
28
+ * client.actor.getActorScrobbles({ did, limit, offset }),
29
+ * pageSize: 50,
30
+ * })) {
31
+ * console.log(s.track.title);
32
+ * }
33
+ */
34
+ export declare function paginate<T>(args: PaginateArgs<T>): AsyncIterable<T> & {
35
+ /** Collect every item into an array — convenience for small result sets. */
36
+ toArray(): Promise<T[]>;
37
+ };
38
+ //# sourceMappingURL=paginate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paginate.d.ts","sourceRoot":"","sources":["../src/paginate.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI;IAC1B,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI;IAC5B;;;;;;OAMG;IACH,KAAK,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,CAAC,CAAC,GAAG;IACrE,4EAA4E;IAC5E,OAAO,IAAI,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;CACzB,CAqCA"}
package/dist/pipe.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Compose async operators.
3
+ *
4
+ * const handle = await pipe(
5
+ * () => client.actor.getProfile({ did }), // thunk — withRetry can re-invoke
6
+ * withRetry(3),
7
+ * withTimeout(5_000),
8
+ * tap(p => console.log("got", p.handle)),
9
+ * map(p => p.handle.toUpperCase()),
10
+ * );
11
+ *
12
+ * `pipe` accepts either a thunk `() => Promise<T>` or a bare `Promise<T>`.
13
+ * Operators take a factory so they can re-invoke the upstream chain (for
14
+ * retry). If you pass a bare Promise, the work has already started and
15
+ * `withRetry` becomes a no-op — pass a thunk if you want true retries.
16
+ */
17
+ export type Op<A, B> = (factory: () => Promise<A>) => () => Promise<B>;
18
+ type Source<A> = Promise<A> | (() => Promise<A>);
19
+ export declare function pipe<A>(value: Source<A>): Promise<A>;
20
+ export declare function pipe<A, B>(value: Source<A>, op1: Op<A, B>): Promise<B>;
21
+ export declare function pipe<A, B, C>(value: Source<A>, op1: Op<A, B>, op2: Op<B, C>): Promise<C>;
22
+ export declare function pipe<A, B, C, D>(value: Source<A>, op1: Op<A, B>, op2: Op<B, C>, op3: Op<C, D>): Promise<D>;
23
+ export declare function pipe<A, B, C, D, E>(value: Source<A>, op1: Op<A, B>, op2: Op<B, C>, op3: Op<C, D>, op4: Op<D, E>): Promise<E>;
24
+ export declare function pipe<A, B, C, D, E, F>(value: Source<A>, op1: Op<A, B>, op2: Op<B, C>, op3: Op<C, D>, op4: Op<D, E>, op5: Op<E, F>): Promise<F>;
25
+ export declare function map<A, B>(fn: (a: A) => B | Promise<B>): Op<A, B>;
26
+ export declare function tap<A>(fn: (a: A) => unknown | Promise<unknown>): Op<A, A>;
27
+ export declare function withRetry<A>(times: number, options?: {
28
+ delayMs?: number;
29
+ factor?: number;
30
+ shouldRetry?: (err: unknown, attempt: number) => boolean;
31
+ }): Op<A, A>;
32
+ export declare function withTimeout<A>(ms: number): Op<A, A>;
33
+ export declare function withFallback<A>(fallback: A | ((err: unknown) => A | Promise<A>)): Op<A, A>;
34
+ export declare function catchError<A, B>(handler: (err: unknown) => B | Promise<B>): Op<A, A | B>;
35
+ export {};
36
+ //# sourceMappingURL=pipe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipe.d.ts","sourceRoot":"","sources":["../src/pipe.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,144 @@
1
+ /** Minimal WebSocket interface — works with browser `WebSocket`, `ws`, `bun`. */
2
+ export interface WebSocketLike {
3
+ readyState: number;
4
+ send(data: string): void;
5
+ close(code?: number, reason?: string): void;
6
+ addEventListener(type: "open", listener: () => void): void;
7
+ addEventListener(type: "close", listener: (event: {
8
+ code: number;
9
+ reason: string;
10
+ }) => void): void;
11
+ addEventListener(type: "error", listener: (event: unknown) => void): void;
12
+ addEventListener(type: "message", listener: (event: {
13
+ data: unknown;
14
+ }) => void): void;
15
+ removeEventListener(type: string, listener: (event: unknown) => void): void;
16
+ }
17
+ export type WebSocketCtor = new (url: string) => WebSocketLike;
18
+ export type ReconnectOptions = {
19
+ backoffMs?: number;
20
+ maxBackoffMs?: number;
21
+ maxAttempts?: number;
22
+ factor?: number;
23
+ };
24
+ export type RealtimeOptions = {
25
+ /** Full WebSocket URL — defaults to ws(s)://api.rocksky.app/ws (derived from baseUrl). */
26
+ url?: string;
27
+ /** Used to derive the WS URL when `url` is not set. Default: DEFAULT_BASE_URL. */
28
+ baseUrl?: string;
29
+ /** JWT token required to register and send messages. */
30
+ token: string | (() => string | Promise<string>);
31
+ /** Display name advertised to the server when registering. */
32
+ clientName?: string;
33
+ /** Auto-register on connect. Default: true. */
34
+ autoRegister?: boolean;
35
+ /** Reconnect on close. Pass `false` to disable. Default: `{ backoffMs: 500, factor: 2, maxBackoffMs: 30000 }`. */
36
+ reconnect?: boolean | ReconnectOptions;
37
+ /** Send `ping` every N ms to keep the socket warm. Default: 25000. Pass 0 to disable. */
38
+ pingIntervalMs?: number;
39
+ /** WebSocket constructor — defaults to global `WebSocket`. Pass a fake for tests. */
40
+ webSocket?: WebSocketCtor;
41
+ };
42
+ export type RealtimeEventMap = {
43
+ open: void;
44
+ close: {
45
+ code: number;
46
+ reason: string;
47
+ };
48
+ error: unknown;
49
+ registered: {
50
+ deviceId: string;
51
+ };
52
+ deviceRegistered: {
53
+ deviceId: string;
54
+ clientName: string;
55
+ };
56
+ message: {
57
+ data: unknown;
58
+ device_id: string;
59
+ };
60
+ control: {
61
+ type: string;
62
+ action: string;
63
+ args?: unknown;
64
+ };
65
+ raw: unknown;
66
+ };
67
+ export type RealtimeEvent = keyof RealtimeEventMap;
68
+ export declare class RealtimeClient {
69
+ private readonly options;
70
+ private ws?;
71
+ private listeners;
72
+ private reconnectAttempts;
73
+ private pingTimer?;
74
+ private closed;
75
+ private deviceId?;
76
+ private readonly url;
77
+ private readonly reconnectOpts;
78
+ private readonly WsCtor;
79
+ constructor(options: RealtimeOptions);
80
+ /** Currently registered device ID (set after the server confirms registration). */
81
+ get currentDeviceId(): string | undefined;
82
+ /** Whether the underlying socket is OPEN. */
83
+ get isOpen(): boolean;
84
+ on<E extends RealtimeEvent>(event: E, listener: (payload: RealtimeEventMap[E]) => void): () => void;
85
+ off<E extends RealtimeEvent>(event: E, listener: (payload: RealtimeEventMap[E]) => void): void;
86
+ private emit;
87
+ /** Open the socket. Resolves once the WebSocket reaches OPEN. */
88
+ connect(): Promise<void>;
89
+ /** Manually register this connection with the server. */
90
+ register(): Promise<void>;
91
+ /**
92
+ * Broadcast a data message to all of the user's connected devices. The
93
+ * server uses `data.type === "track"` for now-playing updates.
94
+ */
95
+ sendMessage(data: Record<string, unknown>): Promise<void>;
96
+ /** Send a control message to one or all devices. */
97
+ sendControl(args: {
98
+ type?: string;
99
+ target?: string;
100
+ action: string;
101
+ args?: unknown;
102
+ }): Promise<void>;
103
+ /** Send a heartbeat ping. Server responds with "pong". */
104
+ ping(): void;
105
+ /** Send a raw message — escape hatch for unsupported message shapes. */
106
+ sendRaw(payload: unknown): void;
107
+ /** Close the socket and disable reconnect. */
108
+ close(code?: number, reason?: string): Promise<void>;
109
+ private attachHandlers;
110
+ private dispatch;
111
+ private scheduleReconnect;
112
+ private startPing;
113
+ private stopPing;
114
+ }
115
+ export declare function createRealtimeClient(opts: RealtimeOptions): RealtimeClient;
116
+ /**
117
+ * Fluent builder for the realtime client.
118
+ *
119
+ * const rt = RealtimeClient.builder()
120
+ * .baseUrl("https://api.rocksky.app")
121
+ * .token(() => loadToken())
122
+ * .clientName("my-app")
123
+ * .pingInterval(20_000)
124
+ * .reconnect({ backoffMs: 1000, maxBackoffMs: 60_000 })
125
+ * .build();
126
+ *
127
+ * await rt.connect();
128
+ */
129
+ export declare class RealtimeClientBuilder {
130
+ private opts;
131
+ url(u: string): this;
132
+ baseUrl(u: string): this;
133
+ token(t: RealtimeOptions["token"]): this;
134
+ clientName(name: string): this;
135
+ autoRegister(value: boolean): this;
136
+ reconnect(value: boolean | ReconnectOptions): this;
137
+ pingInterval(ms: number): this;
138
+ webSocket(ctor: WebSocketCtor): this;
139
+ build(): RealtimeClient;
140
+ }
141
+ export declare namespace RealtimeClient {
142
+ function builder(): RealtimeClientBuilder;
143
+ }
144
+ //# sourceMappingURL=realtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ export type Json = null | boolean | number | string | Json[] | {
2
+ [key: string]: Json;
3
+ };
4
+ export type FetchLike = (input: string | URL, init?: RequestInit) => Promise<Response>;
5
+ export type AuthProvider = string | (() => string | Promise<string>);
6
+ export type ClientOptions = {
7
+ baseUrl?: string;
8
+ auth?: AuthProvider;
9
+ fetch?: FetchLike;
10
+ headers?: Record<string, string>;
11
+ timeoutMs?: number;
12
+ retries?: number;
13
+ retryDelayMs?: number;
14
+ userAgent?: string;
15
+ };
16
+ export type RequestOptions = {
17
+ signal?: AbortSignal;
18
+ headers?: Record<string, string>;
19
+ timeoutMs?: number;
20
+ retries?: number;
21
+ auth?: AuthProvider | null;
22
+ };
23
+ export type Pagination = {
24
+ limit?: number;
25
+ offset?: number;
26
+ cursor?: string;
27
+ };
28
+ export declare const DEFAULT_BASE_URL = "https://api.rocksky.app";
29
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,IAAI,GACZ,IAAI,GACJ,OAAO,GACP,MAAM,GACN,MAAM,GACN,IAAI,EAAE,GACN;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAE5B,MAAM,MAAM,SAAS,GAAG,CACtB,KAAK,EAAE,MAAM,GAAG,GAAG,EACnB,IAAI,CAAC,EAAE,WAAW,KACf,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEvB,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,CAAC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;AAErE,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,gBAAgB,4BAA4B,CAAC"}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@rocksky/sdk",
3
+ "version": "0.1.0",
4
+ "description": "TypeScript SDK for the Rocksky XRPC API",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/tsirysndr/rocksky.git",
9
+ "directory": "sdk/typescript"
10
+ },
11
+ "homepage": "https://github.com/tsirysndr/rocksky/tree/main/sdk/typescript#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/tsirysndr/rocksky/issues"
14
+ },
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "module": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js",
23
+ "default": "./dist/index.js"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "src",
29
+ "README.md"
30
+ ],
31
+ "scripts": {
32
+ "build": "bun build ./src/index.ts --outdir ./dist --target node --format esm --external none && bun run build:types",
33
+ "build:types": "tsc -p tsconfig.build.json",
34
+ "test": "bun test",
35
+ "test:watch": "bun test --watch",
36
+ "typecheck": "tsc --noEmit",
37
+ "example:quickstart": "bun run examples/quickstart.ts",
38
+ "example:builder": "bun run examples/builder.ts",
39
+ "example:pipe": "bun run examples/pipe.ts",
40
+ "example:scrobble": "bun run examples/scrobble.ts",
41
+ "example:pagination": "bun run examples/pagination.ts",
42
+ "example:paginate": "bun run examples/paginate.ts",
43
+ "example:realtime": "bun run examples/realtime.ts"
44
+ },
45
+ "keywords": [
46
+ "rocksky",
47
+ "atproto",
48
+ "at-protocol",
49
+ "bluesky",
50
+ "xrpc",
51
+ "scrobble",
52
+ "scrobbling",
53
+ "music",
54
+ "lastfm",
55
+ "now-playing",
56
+ "sdk",
57
+ "typescript",
58
+ "client",
59
+ "api"
60
+ ],
61
+ "devDependencies": {
62
+ "@types/bun": "latest",
63
+ "typescript": "^5.6.0"
64
+ }
65
+ }
package/src/client.ts ADDED
@@ -0,0 +1,235 @@
1
+ import { buildConfig, type HttpClientConfig, xrpcCall } from "./http";
2
+ import {
3
+ type PaginateArgs,
4
+ paginate as paginateFn,
5
+ } from "./paginate";
6
+ import {
7
+ RealtimeClient,
8
+ type RealtimeOptions,
9
+ createRealtimeClient,
10
+ } from "./realtime";
11
+ import { ActorNamespace } from "./namespaces/actor";
12
+ import { AlbumNamespace } from "./namespaces/album";
13
+ import { ApikeyNamespace } from "./namespaces/apikey";
14
+ import { ArtistNamespace } from "./namespaces/artist";
15
+ import { ChartsNamespace } from "./namespaces/charts";
16
+ import { DropboxNamespace } from "./namespaces/dropbox";
17
+ import { FeedNamespace } from "./namespaces/feed";
18
+ import { GoogleDriveNamespace } from "./namespaces/googledrive";
19
+ import { GraphNamespace } from "./namespaces/graph";
20
+ import { LikeNamespace } from "./namespaces/like";
21
+ import { MirrorNamespace } from "./namespaces/mirror";
22
+ import { PlayerNamespace } from "./namespaces/player";
23
+ import { PlaylistNamespace } from "./namespaces/playlist";
24
+ import { ScrobbleNamespace } from "./namespaces/scrobble";
25
+ import { ShoutNamespace } from "./namespaces/shout";
26
+ import { SongNamespace } from "./namespaces/song";
27
+ import { SpotifyNamespace } from "./namespaces/spotify";
28
+ import { StatsNamespace } from "./namespaces/stats";
29
+ import type { Call } from "./namespaces/_helpers";
30
+ import type {
31
+ AuthProvider,
32
+ ClientOptions,
33
+ FetchLike,
34
+ RequestOptions,
35
+ } from "./types";
36
+
37
+ export class RockskyClient {
38
+ readonly config: HttpClientConfig;
39
+
40
+ readonly actor: ActorNamespace;
41
+ readonly album: AlbumNamespace;
42
+ readonly apikey: ApikeyNamespace;
43
+ readonly artist: ArtistNamespace;
44
+ readonly charts: ChartsNamespace;
45
+ readonly dropbox: DropboxNamespace;
46
+ readonly feed: FeedNamespace;
47
+ readonly googledrive: GoogleDriveNamespace;
48
+ readonly graph: GraphNamespace;
49
+ readonly like: LikeNamespace;
50
+ readonly mirror: MirrorNamespace;
51
+ readonly player: PlayerNamespace;
52
+ readonly playlist: PlaylistNamespace;
53
+ readonly scrobble: ScrobbleNamespace;
54
+ readonly shout: ShoutNamespace;
55
+ readonly song: SongNamespace;
56
+ readonly spotify: SpotifyNamespace;
57
+ readonly stats: StatsNamespace;
58
+
59
+ constructor(options: ClientOptions = {}) {
60
+ this.config = buildConfig(options);
61
+ const call: Call = (nsid, method, opts) =>
62
+ xrpcCall(this.config, nsid, method, opts ?? {});
63
+
64
+ this.actor = new ActorNamespace(call);
65
+ this.album = new AlbumNamespace(call);
66
+ this.apikey = new ApikeyNamespace(call);
67
+ this.artist = new ArtistNamespace(call);
68
+ this.charts = new ChartsNamespace(call);
69
+ this.dropbox = new DropboxNamespace(call);
70
+ this.feed = new FeedNamespace(call);
71
+ this.googledrive = new GoogleDriveNamespace(call);
72
+ this.graph = new GraphNamespace(call);
73
+ this.like = new LikeNamespace(call);
74
+ this.mirror = new MirrorNamespace(call);
75
+ this.player = new PlayerNamespace(call);
76
+ this.playlist = new PlaylistNamespace(call);
77
+ this.scrobble = new ScrobbleNamespace(call);
78
+ this.shout = new ShoutNamespace(call);
79
+ this.song = new SongNamespace(call);
80
+ this.spotify = new SpotifyNamespace(call);
81
+ this.stats = new StatsNamespace(call);
82
+ }
83
+
84
+ /** Build a one-off authenticated copy without mutating this client. */
85
+ withAuth(auth: AuthProvider): RockskyClient {
86
+ return new RockskyClient({
87
+ ...this.optionsSnapshot(),
88
+ auth,
89
+ });
90
+ }
91
+
92
+ /** Build a copy with an overridden base URL. */
93
+ withBaseUrl(baseUrl: string): RockskyClient {
94
+ return new RockskyClient({
95
+ ...this.optionsSnapshot(),
96
+ baseUrl,
97
+ });
98
+ }
99
+
100
+ /**
101
+ * Open a realtime WebSocket connection to /ws.
102
+ *
103
+ * const rt = client.realtime({ token, clientName: "my-app" });
104
+ * rt.on("message", m => console.log(m));
105
+ * await rt.connect();
106
+ *
107
+ * Defaults `baseUrl` to this client's base URL. Override anything by
108
+ * passing the option, or use `RealtimeClient.builder()` for full control.
109
+ */
110
+ realtime(
111
+ options: Omit<RealtimeOptions, "baseUrl"> &
112
+ Partial<Pick<RealtimeOptions, "baseUrl">>,
113
+ ): RealtimeClient {
114
+ return createRealtimeClient({
115
+ baseUrl: this.config.baseUrl,
116
+ ...options,
117
+ });
118
+ }
119
+
120
+ /**
121
+ * Page through any limit/offset or cursor-based endpoint as an async iterable.
122
+ *
123
+ * for await (const s of client.paginate({
124
+ * fetch: ({ limit, offset }) =>
125
+ * client.actor.getActorScrobbles({ did, limit, offset }),
126
+ * pageSize: 50,
127
+ * })) { ... }
128
+ */
129
+ paginate<T>(args: PaginateArgs<T>) {
130
+ return paginateFn(args);
131
+ }
132
+
133
+ /** Direct escape hatch — call any XRPC endpoint by NSID. */
134
+ xrpc<T = unknown>(
135
+ nsid: string,
136
+ method: "GET" | "POST" = "GET",
137
+ opts: {
138
+ params?: Record<string, unknown>;
139
+ body?: unknown;
140
+ requireAuth?: boolean;
141
+ } & RequestOptions = {},
142
+ ): Promise<T> {
143
+ return xrpcCall<T>(this.config, nsid, method, opts);
144
+ }
145
+
146
+ static builder(): RockskyClientBuilder {
147
+ return new RockskyClientBuilder();
148
+ }
149
+
150
+ private optionsSnapshot(): ClientOptions {
151
+ return {
152
+ baseUrl: this.config.baseUrl,
153
+ auth: this.config.auth,
154
+ fetch: this.config.fetch,
155
+ headers: this.config.headers,
156
+ timeoutMs: this.config.timeoutMs,
157
+ retries: this.config.retries,
158
+ retryDelayMs: this.config.retryDelayMs,
159
+ };
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Fluent builder.
165
+ *
166
+ * const client = RockskyClient.builder()
167
+ * .baseUrl("https://api.rocksky.app")
168
+ * .auth(() => loadToken())
169
+ * .timeout(10_000)
170
+ * .retries(3)
171
+ * .userAgent("my-app/1.0")
172
+ * .build();
173
+ */
174
+ export class RockskyClientBuilder {
175
+ private readonly opts: ClientOptions = {};
176
+
177
+ baseUrl(url: string): this {
178
+ this.opts.baseUrl = url;
179
+ return this;
180
+ }
181
+
182
+ auth(auth: AuthProvider): this {
183
+ this.opts.auth = auth;
184
+ return this;
185
+ }
186
+
187
+ bearer(token: string): this {
188
+ this.opts.auth = token;
189
+ return this;
190
+ }
191
+
192
+ fetch(impl: FetchLike): this {
193
+ this.opts.fetch = impl;
194
+ return this;
195
+ }
196
+
197
+ header(key: string, value: string): this {
198
+ this.opts.headers = { ...this.opts.headers, [key]: value };
199
+ return this;
200
+ }
201
+
202
+ headers(headers: Record<string, string>): this {
203
+ this.opts.headers = { ...this.opts.headers, ...headers };
204
+ return this;
205
+ }
206
+
207
+ userAgent(ua: string): this {
208
+ this.opts.userAgent = ua;
209
+ return this;
210
+ }
211
+
212
+ timeout(ms: number): this {
213
+ this.opts.timeoutMs = ms;
214
+ return this;
215
+ }
216
+
217
+ retries(n: number): this {
218
+ this.opts.retries = n;
219
+ return this;
220
+ }
221
+
222
+ retryDelay(ms: number): this {
223
+ this.opts.retryDelayMs = ms;
224
+ return this;
225
+ }
226
+
227
+ build(): RockskyClient {
228
+ return new RockskyClient(this.opts);
229
+ }
230
+ }
231
+
232
+ /** Convenience factory — equivalent to `new RockskyClient(options)`. */
233
+ export function createClient(options: ClientOptions = {}): RockskyClient {
234
+ return new RockskyClient(options);
235
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,47 @@
1
+ export class RockskyError extends Error {
2
+ readonly cause?: unknown;
3
+ constructor(message: string, options?: { cause?: unknown }) {
4
+ super(message);
5
+ this.name = "RockskyError";
6
+ this.cause = options?.cause;
7
+ }
8
+ }
9
+
10
+ export class RockskyHttpError extends RockskyError {
11
+ readonly status: number;
12
+ readonly statusText: string;
13
+ readonly url: string;
14
+ readonly body: unknown;
15
+
16
+ constructor(args: {
17
+ status: number;
18
+ statusText: string;
19
+ url: string;
20
+ body: unknown;
21
+ message?: string;
22
+ }) {
23
+ const message =
24
+ args.message ??
25
+ `Rocksky API ${args.status} ${args.statusText} at ${args.url}`;
26
+ super(message);
27
+ this.name = "RockskyHttpError";
28
+ this.status = args.status;
29
+ this.statusText = args.statusText;
30
+ this.url = args.url;
31
+ this.body = args.body;
32
+ }
33
+ }
34
+
35
+ export class RockskyTimeoutError extends RockskyError {
36
+ constructor(ms: number) {
37
+ super(`Rocksky request timed out after ${ms}ms`);
38
+ this.name = "RockskyTimeoutError";
39
+ }
40
+ }
41
+
42
+ export class RockskyAuthError extends RockskyError {
43
+ constructor(message = "Authentication required") {
44
+ super(message);
45
+ this.name = "RockskyAuthError";
46
+ }
47
+ }