@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,19 @@
1
+ // shared types for domain method factories
2
+
3
+ import { z } from "zod";
4
+
5
+ // result types
6
+ type SafeParseSuccess<T> = { success: true; data: T };
7
+ type SafeParseError = { success: false; error: z.ZodError };
8
+ export type SafeParseResult<T> = SafeParseSuccess<T> | SafeParseError;
9
+
10
+ // the call function signature used by domain methods
11
+ export type CallFn = <Resp>(
12
+ domain: string,
13
+ routeName: string,
14
+ respSchema: z.ZodType<Resp> | null,
15
+ reqSchema: z.ZodTypeAny | null,
16
+ method: string,
17
+ path: string,
18
+ params?: any,
19
+ ) => Promise<SafeParseResult<Resp>>;
@@ -0,0 +1,214 @@
1
+ // upload domain methods for FreqholeClient
2
+ // uses transport.upload() for FormData handling
3
+
4
+ import { z } from "zod";
5
+ import type * as s from "../codegen/schema.js";
6
+ import {
7
+ ImageUploadResponseSchema,
8
+ MusicImportResponseSchema,
9
+ MusicUploadResponseSchema,
10
+ } from "../codegen/schema.js";
11
+ import type { Transport } from "../transport.js";
12
+ import type { SafeParseResult } from "./types.js";
13
+
14
+ // helper to parse response and validate with schema
15
+ function parseResponse<T>(
16
+ responseBody: string,
17
+ status: number,
18
+ schema: z.ZodType<T>,
19
+ ): SafeParseResult<T> {
20
+ if (status >= 400) {
21
+ let errorMessage = `HTTP ${status}`;
22
+ try {
23
+ const errorBody = JSON.parse(responseBody);
24
+ if (errorBody?.error) {
25
+ errorMessage = `HTTP ${status}: ${errorBody.error}`;
26
+ }
27
+ } catch {
28
+ // body wasn't JSON
29
+ }
30
+ return {
31
+ success: false,
32
+ error: new z.ZodError([
33
+ { code: "custom", path: [], message: errorMessage },
34
+ ]),
35
+ };
36
+ }
37
+
38
+ try {
39
+ const json = JSON.parse(responseBody);
40
+ const data = json.data ?? json;
41
+ const result = schema.safeParse(data);
42
+ if (result.success) {
43
+ return { success: true, data: result.data };
44
+ } else {
45
+ return { success: false, error: result.error };
46
+ }
47
+ } catch (err) {
48
+ return {
49
+ success: false,
50
+ error: new z.ZodError([
51
+ {
52
+ code: "custom",
53
+ path: [],
54
+ message: err instanceof Error ? err.message : "parse error",
55
+ },
56
+ ]),
57
+ };
58
+ }
59
+ }
60
+
61
+ export type UploadImageOptions = {
62
+ /** optionally associate the image with an entity (album, playlist, song, artist, etc.) */
63
+ associate?: s.AssociationHint;
64
+ };
65
+
66
+ export function createUploadMethods(transport: Transport) {
67
+ return {
68
+ /**
69
+ * upload a music file
70
+ * returns job information for async processing
71
+ */
72
+ music: async (
73
+ file: File | Blob,
74
+ ): Promise<SafeParseResult<s.MusicUploadResponse>> => {
75
+ const formData = new FormData();
76
+ formData.append("file", file);
77
+
78
+ const response = await transport.upload("/api/upload/music", formData);
79
+ return parseResponse(
80
+ response.body,
81
+ response.status,
82
+ MusicUploadResponseSchema,
83
+ );
84
+ },
85
+
86
+ /**
87
+ * upload a music file by filesystem path (tauri P2P only).
88
+ * imports the file into the local iroh-blobs store, then tells the remote
89
+ * peer to pull it via verified streaming.
90
+ * requires transport.uploadByPath to be implemented (CharnelTransport).
91
+ */
92
+ musicByPath: async (
93
+ filePath: string,
94
+ ): Promise<SafeParseResult<s.MusicUploadResponse>> => {
95
+ if (!transport.uploadByPath) {
96
+ return {
97
+ success: false,
98
+ error: new z.ZodError([
99
+ {
100
+ code: "custom",
101
+ path: [],
102
+ message: "uploadByPath not supported by this transport",
103
+ },
104
+ ]),
105
+ };
106
+ }
107
+
108
+ const response = await transport.uploadByPath(
109
+ "/api/upload/music",
110
+ filePath,
111
+ );
112
+ return parseResponse(
113
+ response.body,
114
+ response.status,
115
+ MusicUploadResponseSchema,
116
+ );
117
+ },
118
+
119
+ /**
120
+ * upload an image file
121
+ * optionally associate with an entity (album, playlist, song, artist)
122
+ */
123
+ image: async (
124
+ file: File | Blob,
125
+ options?: UploadImageOptions,
126
+ ): Promise<SafeParseResult<s.ImageUploadResponse>> => {
127
+ const formData = new FormData();
128
+ formData.append("file", file);
129
+
130
+ if (options?.associate) {
131
+ formData.append("associate_with", JSON.stringify(options.associate));
132
+ }
133
+
134
+ const response = await transport.upload("/api/upload/image", formData);
135
+ return parseResponse(
136
+ response.body,
137
+ response.status,
138
+ ImageUploadResponseSchema,
139
+ );
140
+ },
141
+
142
+ /**
143
+ * upload an image by filesystem path (tauri-local only)
144
+ * bypasses base64 encoding by passing path directly to backend
145
+ * requires transport.uploadByPath to be implemented
146
+ */
147
+ imageByPath: async (
148
+ filePath: string,
149
+ options?: UploadImageOptions,
150
+ ): Promise<SafeParseResult<s.ImageUploadResponse>> => {
151
+ if (!transport.uploadByPath) {
152
+ return {
153
+ success: false,
154
+ error: new z.ZodError([
155
+ {
156
+ code: "custom",
157
+ path: [],
158
+ message: "uploadByPath not supported by this transport",
159
+ },
160
+ ]),
161
+ };
162
+ }
163
+
164
+ const metadata: Record<string, unknown> = {};
165
+ if (options?.associate) {
166
+ metadata.associate_with = options.associate;
167
+ }
168
+
169
+ const response = await transport.uploadByPath(
170
+ "/api/upload/image",
171
+ filePath,
172
+ metadata,
173
+ );
174
+ return parseResponse(
175
+ response.body,
176
+ response.status,
177
+ ImageUploadResponseSchema,
178
+ );
179
+ },
180
+
181
+ /**
182
+ * import music files by filesystem paths (tauri-local only)
183
+ * accepts file paths or directory paths (directories are scanned recursively)
184
+ * bypasses file transfer since files are already local
185
+ * requires transport.request to be implemented (uses POST with JSON body)
186
+ *
187
+ * @param paths - array of file or directory paths to import
188
+ * @param options - optional settings
189
+ * @param options.waitForCompletion - if true, wait for all jobs to complete (up to 5 min)
190
+ */
191
+ musicByPaths: async (
192
+ paths: string[],
193
+ options?: { waitForCompletion?: boolean },
194
+ ): Promise<SafeParseResult<s.MusicImportResponse>> => {
195
+ const body = {
196
+ paths,
197
+ wait_for_completion: options?.waitForCompletion ?? false,
198
+ };
199
+
200
+ const response = await transport.request(
201
+ "POST",
202
+ "/api/upload/music-paths",
203
+ JSON.stringify(body),
204
+ );
205
+ return parseResponse(
206
+ response.body,
207
+ response.status,
208
+ MusicImportResponseSchema,
209
+ );
210
+ },
211
+ };
212
+ }
213
+
214
+ export type UploadMethods = ReturnType<typeof createUploadMethods>;
package/src/index.ts ADDED
@@ -0,0 +1,48 @@
1
+ // freqhole api client - main exports
2
+
3
+ // instance-based client
4
+ export { FreqholeClient, createClient, createHttpClient, isAuthError, isNetworkError } from "./FreqholeClient.js";
5
+ export type { SafeParseResult } from "./FreqholeClient.js";
6
+
7
+ // transport abstraction
8
+ export { HttpTransport, pollingJobEvents, snapshotJobEventsViaRequest, POLL_INTERVAL_FALLBACK_MS } from "./transport.js";
9
+ export { WasmTransport } from "./WasmTransport.js";
10
+ export { CharnelTransport, createCharnelTransport, getCharnelTransport, getCharnelNodeId, isCharnelAvailable, isCharnelP2PAvailable } from "./CharnelTransport.js";
11
+ export { CharnelLocalTransport, createCharnelLocalTransport, JobEventsStreamClosed } from "./CharnelLocalTransport.js";
12
+ export type { MiddenNodeLike, BlobResultLike, BlobProgressCallback, RadioHandleLike } from "./WasmTransport.js";
13
+ export type { Transport, TransportResponse, BlobData } from "./transport.js";
14
+
15
+ // export utilities (url helpers, uploads, etc)
16
+ export * as utils from "./utils.js";
17
+
18
+ // export webauthn helpers
19
+ export * as webauthn from "./webauthn.js";
20
+
21
+ // export permission helpers
22
+ export * as permissions from "./permissions.js";
23
+
24
+ // export hand-rolled favorites types (codegen doesn't handle discriminated unions)
25
+ export type { FavoriteItem, ListFavoritesResponse } from "./domains/favorites.types.js";
26
+
27
+ // export schemas and types
28
+ export type * from "./codegen/schema.js";
29
+ export * as schema from "./codegen/schema.js";
30
+
31
+ // export route auth types
32
+ export type { RouteAuth, RouteAuthType, UserRoleName } from "./codegen/routes.js";
33
+ export { roleHierarchy } from "./codegen/routes.js";
34
+
35
+ // export admin client (freqhole-admin/1 ALPN)
36
+ export {
37
+ AdminClient,
38
+ AdminCommandError,
39
+ AdminRequestValidationError,
40
+ AdminResponseValidationError,
41
+ } from "./AdminClient.js";
42
+ export type {
43
+ AdminTransport,
44
+ AdminResponse,
45
+ AdminErrorDetail,
46
+ } from "./AdminClient.js";
47
+ export { adminCommands } from "./codegen/admin_commands.js";
48
+ export type { AdminCommandName, AdminAuth, AdminAuthType } from "./codegen/admin_commands.js";