@freqhole/api-client 0.1.30

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 (45) hide show
  1. package/auth-test.html +651 -0
  2. package/package.json +42 -0
  3. package/src/AdminClient.ts +154 -0
  4. package/src/CharnelLocalTransport.ts +598 -0
  5. package/src/CharnelTransport.ts +616 -0
  6. package/src/FreqholeClient.ts +261 -0
  7. package/src/WasmTransport.ts +1077 -0
  8. package/src/automerge/IrohNetworkAdapter.ts +727 -0
  9. package/src/automerge/index.ts +14 -0
  10. package/src/codegen/admin_commands.ts +61 -0
  11. package/src/codegen/routes.ts +213 -0
  12. package/src/codegen/schema.ts +6721 -0
  13. package/src/domains/admin.ts +97 -0
  14. package/src/domains/app.ts +98 -0
  15. package/src/domains/auth.ts +191 -0
  16. package/src/domains/favorites.types.ts +52 -0
  17. package/src/domains/jobs.ts +48 -0
  18. package/src/domains/music.ts +1935 -0
  19. package/src/domains/types.ts +19 -0
  20. package/src/domains/upload.ts +214 -0
  21. package/src/index.ts +48 -0
  22. package/src/permissions.ts +468 -0
  23. package/src/playlistz/convert.ts +267 -0
  24. package/src/playlistz/index.ts +6 -0
  25. package/src/playlistz/mutations.ts +181 -0
  26. package/src/playlistz/protocol.ts +222 -0
  27. package/src/playlistz/schema.ts +138 -0
  28. package/src/playlistz/shareLink.ts +76 -0
  29. package/src/storage/blobs.ts +272 -0
  30. package/src/storage/identity.ts +190 -0
  31. package/src/storage/index.ts +5 -0
  32. package/src/storage/webLocks.ts +92 -0
  33. package/src/test/automerge.ts +796 -0
  34. package/src/test/coverage.ts +105 -0
  35. package/src/test/fixtures.ts +460 -0
  36. package/src/test/integration.ts +320 -0
  37. package/src/test/playlistz.ts +631 -0
  38. package/src/test/stateful.ts +561 -0
  39. package/src/test/storage.ts +443 -0
  40. package/src/test.ts +75 -0
  41. package/src/transport.ts +391 -0
  42. package/src/utils.ts +79 -0
  43. package/src/webauthn.ts +228 -0
  44. package/tsconfig.json +22 -0
  45. package/vite.config.js +9 -0
@@ -0,0 +1,97 @@
1
+ // admin domain methods for FreqholeClient
2
+ // includes knock management endpoints
3
+
4
+ import { routes } from "../codegen/routes.js";
5
+ import type * as s from "../codegen/schema.js";
6
+ import type { CallFn } from "./types.js";
7
+
8
+ export function createAdminMethods(call: CallFn) {
9
+ return {
10
+ // knock management (admin)
11
+ listKnocks: () => {
12
+ return call(
13
+ "admin", "list_knocks",
14
+ routes.admin.list_knocks.resp,
15
+ routes.admin.list_knocks.req,
16
+ routes.admin.list_knocks.method,
17
+ routes.admin.list_knocks.path,
18
+ );
19
+ },
20
+
21
+ listAllKnocks: () => {
22
+ return call(
23
+ "admin", "list_all_knocks",
24
+ routes.admin.list_all_knocks.resp,
25
+ routes.admin.list_all_knocks.req,
26
+ routes.admin.list_all_knocks.method,
27
+ routes.admin.list_all_knocks.path,
28
+ );
29
+ },
30
+
31
+ getKnock: (params: { id: string }) => {
32
+ return call(
33
+ "admin", "get_knock",
34
+ routes.admin.get_knock.resp,
35
+ routes.admin.get_knock.req,
36
+ routes.admin.get_knock.method,
37
+ routes.admin.get_knock.path,
38
+ params,
39
+ );
40
+ },
41
+
42
+ acceptKnock: (params: { id: string } & s.ProcessKnockRequest) => {
43
+ return call(
44
+ "admin", "accept_knock",
45
+ routes.admin.accept_knock.resp,
46
+ routes.admin.accept_knock.req,
47
+ routes.admin.accept_knock.method,
48
+ routes.admin.accept_knock.path,
49
+ params,
50
+ );
51
+ },
52
+
53
+ rejectKnock: (params: { id: string }) => {
54
+ return call(
55
+ "admin", "reject_knock",
56
+ routes.admin.reject_knock.resp,
57
+ routes.admin.reject_knock.req,
58
+ routes.admin.reject_knock.method,
59
+ routes.admin.reject_knock.path,
60
+ params,
61
+ );
62
+ },
63
+
64
+ deleteKnock: (params: { id: string }) => {
65
+ return call(
66
+ "admin", "delete_knock",
67
+ routes.admin.delete_knock.resp,
68
+ routes.admin.delete_knock.req,
69
+ routes.admin.delete_knock.method,
70
+ routes.admin.delete_knock.path,
71
+ params,
72
+ );
73
+ },
74
+
75
+ // public knock endpoints (for P2P access requests)
76
+ createKnockPublic: (params: s.CreateKnockRequest) => {
77
+ return call(
78
+ "admin", "create_knock_public",
79
+ routes.admin.create_knock_public.resp,
80
+ routes.admin.create_knock_public.req,
81
+ routes.admin.create_knock_public.method,
82
+ routes.admin.create_knock_public.path,
83
+ params,
84
+ );
85
+ },
86
+
87
+ getKnockStatusPublic: () => {
88
+ return call(
89
+ "admin", "get_knock_status_public",
90
+ routes.admin.get_knock_status_public.resp,
91
+ routes.admin.get_knock_status_public.req,
92
+ routes.admin.get_knock_status_public.method,
93
+ routes.admin.get_knock_status_public.path,
94
+ );
95
+ },
96
+ };
97
+ }
@@ -0,0 +1,98 @@
1
+ // app domain methods for FreqholeClient
2
+
3
+ import { routes } from "../codegen/routes.js";
4
+ import type { CallFn } from "./types.js";
5
+
6
+ export function createAppMethods(call: CallFn) {
7
+ return {
8
+ healthCheck: () => {
9
+ return call(
10
+ "app", "health_check",
11
+ routes.app.health_check.resp,
12
+ routes.app.health_check.req,
13
+ routes.app.health_check.method,
14
+ routes.app.health_check.path,
15
+ );
16
+ },
17
+
18
+ serverInfo: () => {
19
+ return call(
20
+ "app", "server_info",
21
+ routes.app.server_info.resp,
22
+ routes.app.server_info.req,
23
+ routes.app.server_info.method,
24
+ routes.app.server_info.path,
25
+ );
26
+ },
27
+
28
+ radioInfo: () => {
29
+ return call(
30
+ "app", "radio_info",
31
+ routes.app.radio_info.resp,
32
+ routes.app.radio_info.req,
33
+ routes.app.radio_info.method,
34
+ routes.app.radio_info.path,
35
+ );
36
+ },
37
+
38
+ radioStations: () => {
39
+ return call(
40
+ "app", "radio_stations",
41
+ routes.app.radio_stations.resp,
42
+ routes.app.radio_stations.req,
43
+ routes.app.radio_stations.method,
44
+ routes.app.radio_stations.path,
45
+ );
46
+ },
47
+
48
+ // get blob metadata for a station blob
49
+ radioPublicBlob: (params: { station_id: string; blob_id: string }) => {
50
+ return call(
51
+ "app", "radio_public_blob",
52
+ routes.app.radio_public_blob.resp,
53
+ routes.app.radio_public_blob.req,
54
+ routes.app.radio_public_blob.method,
55
+ routes.app.radio_public_blob.path,
56
+ params,
57
+ );
58
+ },
59
+
60
+ // stream the binary data for a station blob
61
+ radioPublicBlobData: (params: { station_id: string; blob_id: string }) => {
62
+ return call(
63
+ "app", "radio_public_blob_data",
64
+ routes.app.radio_public_blob_data.resp,
65
+ routes.app.radio_public_blob_data.req,
66
+ routes.app.radio_public_blob_data.method,
67
+ routes.app.radio_public_blob_data.path,
68
+ params,
69
+ );
70
+ },
71
+
72
+ // get a thumbnail for a station blob at a given size
73
+ radioPublicBlobThumbnail: (params: { station_id: string; blob_id: string; size: string }) => {
74
+ return call(
75
+ "app", "radio_public_blob_thumbnail",
76
+ routes.app.radio_public_blob_thumbnail.resp,
77
+ routes.app.radio_public_blob_thumbnail.req,
78
+ routes.app.radio_public_blob_thumbnail.method,
79
+ routes.app.radio_public_blob_thumbnail.path,
80
+ params,
81
+ );
82
+ },
83
+
84
+ // get the public timeline manifest for a station
85
+ radioPublicTimeline: (params: { station_id: string }) => {
86
+ return call(
87
+ "app", "radio_public_timeline",
88
+ routes.app.radio_public_timeline.resp,
89
+ routes.app.radio_public_timeline.req,
90
+ routes.app.radio_public_timeline.method,
91
+ routes.app.radio_public_timeline.path,
92
+ params,
93
+ );
94
+ },
95
+ };
96
+ }
97
+
98
+ export type AppMethods = ReturnType<typeof createAppMethods>;
@@ -0,0 +1,191 @@
1
+ // auth domain methods for FreqholeClient
2
+
3
+ import { routes } from "../codegen/routes.js";
4
+ import type * as s from "../codegen/schema.js";
5
+ import type { CallFn } from "./types.js";
6
+
7
+ export function createAuthMethods(call: CallFn) {
8
+ return {
9
+ registerStart: (params: s.RegisterStartRequest) => {
10
+ return call(
11
+ "auth", "register_start",
12
+ routes.auth.register_start.resp,
13
+ routes.auth.register_start.req,
14
+ routes.auth.register_start.method,
15
+ routes.auth.register_start.path,
16
+ params,
17
+ );
18
+ },
19
+
20
+ registerFinish: (params: any) => {
21
+ return call(
22
+ "auth", "register_finish",
23
+ routes.auth.register_finish.resp,
24
+ routes.auth.register_finish.req,
25
+ routes.auth.register_finish.method,
26
+ routes.auth.register_finish.path,
27
+ params,
28
+ );
29
+ },
30
+
31
+ loginStart: (params: s.StartLoginRequest) => {
32
+ return call(
33
+ "auth", "login_start",
34
+ routes.auth.login_start.resp,
35
+ routes.auth.login_start.req,
36
+ routes.auth.login_start.method,
37
+ routes.auth.login_start.path,
38
+ params,
39
+ );
40
+ },
41
+
42
+ loginFinish: (params: any) => {
43
+ return call(
44
+ "auth", "login_finish",
45
+ routes.auth.login_finish.resp,
46
+ routes.auth.login_finish.req,
47
+ routes.auth.login_finish.method,
48
+ routes.auth.login_finish.path,
49
+ params,
50
+ );
51
+ },
52
+
53
+ whoami: () => {
54
+ return call(
55
+ "auth", "whoami",
56
+ routes.auth.whoami.resp,
57
+ routes.auth.whoami.req,
58
+ routes.auth.whoami.method,
59
+ routes.auth.whoami.path,
60
+ );
61
+ },
62
+
63
+ logout: () => {
64
+ return call(
65
+ "auth", "logout",
66
+ routes.auth.logout.resp,
67
+ routes.auth.logout.req,
68
+ routes.auth.logout.method,
69
+ routes.auth.logout.path,
70
+ );
71
+ },
72
+
73
+ regenerateApiKey: () => {
74
+ return call(
75
+ "auth", "regenerate_api_key",
76
+ routes.auth.regenerate_api_key.resp,
77
+ routes.auth.regenerate_api_key.req,
78
+ routes.auth.regenerate_api_key.method,
79
+ routes.auth.regenerate_api_key.path,
80
+ );
81
+ },
82
+
83
+ apiKeyStatus: () => {
84
+ return call(
85
+ "auth", "api_key_status",
86
+ routes.auth.api_key_status.resp,
87
+ routes.auth.api_key_status.req,
88
+ routes.auth.api_key_status.method,
89
+ routes.auth.api_key_status.path,
90
+ );
91
+ },
92
+
93
+ redeemInvite: (params: s.RedeemInviteRequest) => {
94
+ return call(
95
+ "auth", "redeem_invite",
96
+ routes.auth.redeem_invite.resp,
97
+ routes.auth.redeem_invite.req,
98
+ routes.auth.redeem_invite.method,
99
+ routes.auth.redeem_invite.path,
100
+ params,
101
+ );
102
+ },
103
+
104
+ listPasskeys: () => {
105
+ return call(
106
+ "auth", "list_passkeys",
107
+ routes.auth.list_passkeys.resp,
108
+ routes.auth.list_passkeys.req,
109
+ routes.auth.list_passkeys.method,
110
+ routes.auth.list_passkeys.path,
111
+ );
112
+ },
113
+
114
+ deletePasskey: (params: s.DeletePasskeyRequest) => {
115
+ return call(
116
+ "auth", "delete_passkey",
117
+ routes.auth.delete_passkey.resp,
118
+ routes.auth.delete_passkey.req,
119
+ routes.auth.delete_passkey.method,
120
+ routes.auth.delete_passkey.path,
121
+ params,
122
+ );
123
+ },
124
+
125
+ linkNode: (params: s.LinkNodeRequest) => {
126
+ return call(
127
+ "auth", "link_node",
128
+ routes.auth.link_node.resp,
129
+ routes.auth.link_node.req,
130
+ routes.auth.link_node.method,
131
+ routes.auth.link_node.path,
132
+ params,
133
+ );
134
+ },
135
+
136
+ updateUsername: (params: s.UpdateUsernameRequest) => {
137
+ return call(
138
+ "auth", "update_username",
139
+ routes.auth.update_username.resp,
140
+ routes.auth.update_username.req,
141
+ routes.auth.update_username.method,
142
+ routes.auth.update_username.path,
143
+ params,
144
+ );
145
+ },
146
+
147
+ generateSelfAccountLink: () => {
148
+ return call(
149
+ "auth", "generate_self_account_link",
150
+ routes.auth.generate_self_account_link.resp,
151
+ routes.auth.generate_self_account_link.req,
152
+ routes.auth.generate_self_account_link.method,
153
+ routes.auth.generate_self_account_link.path,
154
+ );
155
+ },
156
+
157
+ listOwnInvites: () => {
158
+ return call(
159
+ "auth", "list_own_invites",
160
+ routes.auth.list_own_invites.resp,
161
+ routes.auth.list_own_invites.req,
162
+ routes.auth.list_own_invites.method,
163
+ routes.auth.list_own_invites.path,
164
+ );
165
+ },
166
+
167
+ revokeOwnInvite: (params: s.RevokeOwnInviteRequest) => {
168
+ return call(
169
+ "auth", "revoke_own_invite",
170
+ routes.auth.revoke_own_invite.resp,
171
+ routes.auth.revoke_own_invite.req,
172
+ routes.auth.revoke_own_invite.method,
173
+ routes.auth.revoke_own_invite.path,
174
+ params,
175
+ );
176
+ },
177
+
178
+ updatePasskeyName: (params: s.UpdatePasskeyNameRequest) => {
179
+ return call(
180
+ "auth", "update_passkey_name",
181
+ routes.auth.update_passkey_name.resp,
182
+ routes.auth.update_passkey_name.req,
183
+ routes.auth.update_passkey_name.method,
184
+ routes.auth.update_passkey_name.path,
185
+ params,
186
+ );
187
+ },
188
+ };
189
+ }
190
+
191
+ export type AuthMethods = ReturnType<typeof createAuthMethods>;
@@ -0,0 +1,52 @@
1
+ // hand-rolled favorites types with proper discriminated union support
2
+ // the codegen doesn't handle rust's #[serde(tag = "type")] properly yet
3
+ import { z } from "zod";
4
+ import {
5
+ SongQueryResultSchema,
6
+ AlbumQueryResultSchema,
7
+ ArtistQueryResultSchema,
8
+ PlaylistQueryResultSchema,
9
+ } from "../codegen/schema.js";
10
+
11
+ // discriminated union for favorite items
12
+ // matches rust's FavoriteItem enum with #[serde(tag = "type", rename_all = "lowercase")]
13
+ const FavoriteItemSchema = z.discriminatedUnion("type", [
14
+ z.object({
15
+ type: z.literal("song"),
16
+ favorited_at: z.number(),
17
+ song: SongQueryResultSchema,
18
+ }),
19
+ z.object({
20
+ type: z.literal("album"),
21
+ favorited_at: z.number(),
22
+ album: AlbumQueryResultSchema,
23
+ }),
24
+ z.object({
25
+ type: z.literal("artist"),
26
+ favorited_at: z.number(),
27
+ artist: ArtistQueryResultSchema,
28
+ }),
29
+ z.object({
30
+ type: z.literal("playlist"),
31
+ favorited_at: z.number(),
32
+ playlist: PlaylistQueryResultSchema,
33
+ }),
34
+ ]);
35
+
36
+ export type FavoriteItem = z.infer<typeof FavoriteItemSchema>;
37
+
38
+ // properly typed response from list_favorites endpoint
39
+ const ListFavoritesResponseSchema = z.object({
40
+ favorites: z.array(FavoriteItemSchema),
41
+ total_count: z.number(),
42
+ has_more: z.boolean(),
43
+ offset: z.number(),
44
+ limit: z.number(),
45
+ });
46
+
47
+ export type ListFavoritesResponse = z.infer<
48
+ typeof ListFavoritesResponseSchema
49
+ >;
50
+
51
+ // export schemas for runtime validation if needed
52
+ export { FavoriteItemSchema, ListFavoritesResponseSchema };
@@ -0,0 +1,48 @@
1
+ // jobs domain methods for FreqholeClient.
2
+ //
3
+ // thin wrappers over the transport's `snapshotJobEvents` /
4
+ // `subscribeJobEvents` capability. transports that don't implement
5
+ // these fall back to the http polling iterator (see transport.ts).
6
+
7
+ import type { Transport } from "../transport.js";
8
+ import {
9
+ pollingJobEvents,
10
+ snapshotJobEventsViaRequest,
11
+ } from "../transport.js";
12
+ import type {
13
+ EventFilter,
14
+ JobEvent,
15
+ JobStateSnapshot,
16
+ } from "../codegen/schema.js";
17
+
18
+ export function createJobsMethods(transport: Transport) {
19
+ return {
20
+ events: {
21
+ /**
22
+ * one-shot snapshot of currently-active jobs the caller can see.
23
+ * use this to rehydrate state on mount / after a page reload.
24
+ */
25
+ snapshot: async (filter?: EventFilter): Promise<JobStateSnapshot[]> => {
26
+ if (transport.snapshotJobEvents) {
27
+ return transport.snapshotJobEvents(filter);
28
+ }
29
+ return snapshotJobEventsViaRequest(transport, filter);
30
+ },
31
+
32
+ /**
33
+ * live subscription to job events. iterates `JobEvent`s until the
34
+ * stream ends or the optional `signal` is aborted. transports
35
+ * without native streaming use the http polling fallback.
36
+ */
37
+ subscribe: (
38
+ filter?: EventFilter,
39
+ signal?: AbortSignal,
40
+ ): AsyncIterable<JobEvent> => {
41
+ if (transport.subscribeJobEvents) {
42
+ return transport.subscribeJobEvents(filter, signal);
43
+ }
44
+ return pollingJobEvents(transport, filter, signal);
45
+ },
46
+ },
47
+ };
48
+ }