@freqhole/api-client 0.2.15 → 0.3.1
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.
- package/package.json +1 -1
- package/src/CharnelLocalTransport.ts +70 -35
- package/src/CharnelTransport.ts +391 -106
- package/src/FreqholeClient.ts +46 -83
- package/src/WasmTransport.ts +265 -219
- package/src/blobCachePolicy.test.ts +199 -0
- package/src/codegen/routes.ts +75 -13
- package/src/codegen/schema.ts +1436 -225
- package/src/domains/entities.ts +304 -0
- package/src/domains/favorites.types.ts +13 -3
- package/src/domains/music.ts +74 -126
- package/src/domains/upload.ts +54 -53
- package/src/domains/video.ts +463 -0
- package/src/errors.ts +142 -0
- package/src/index.ts +47 -10
- package/src/permissions.ts +18 -36
- package/src/tauriRuntime.ts +22 -0
- package/src/test/fixtures.ts +13 -23
- package/src/test/integration.ts +15 -28
- package/src/test/stateful.ts +11 -29
- package/src/transport.ts +72 -39
package/src/FreqholeClient.ts
CHANGED
|
@@ -6,20 +6,20 @@
|
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import type { Transport, BlobData } from "./transport.js";
|
|
8
8
|
import { HttpTransport } from "./transport.js";
|
|
9
|
+
import { AUTH_ERROR_PATH, toZodError } from "./errors.js";
|
|
9
10
|
import { createAdminMethods } from "./domains/admin.js";
|
|
10
11
|
import { createAppMethods } from "./domains/app.js";
|
|
11
12
|
import { createAuthMethods } from "./domains/auth.js";
|
|
13
|
+
import { createEntitiesMethods } from "./domains/entities.js";
|
|
12
14
|
import { createJobsMethods } from "./domains/jobs.js";
|
|
13
15
|
import { createMusicMethods } from "./domains/music.js";
|
|
14
16
|
import { createUploadMethods } from "./domains/upload.js";
|
|
17
|
+
import { createVideoMethods } from "./domains/video.js";
|
|
15
18
|
import type { CallFn, SafeParseResult } from "./domains/types.js";
|
|
16
19
|
|
|
17
20
|
// re-export types for consumers
|
|
18
21
|
export type { SafeParseResult } from "./domains/types.js";
|
|
19
22
|
|
|
20
|
-
// sentinel for 401 detection
|
|
21
|
-
const AUTH_ERROR_PATH = "__auth_expired__";
|
|
22
|
-
|
|
23
23
|
// ============================================================================
|
|
24
24
|
// FreqholeClient
|
|
25
25
|
// ============================================================================
|
|
@@ -28,18 +28,22 @@ export class FreqholeClient {
|
|
|
28
28
|
public readonly admin: ReturnType<typeof createAdminMethods>;
|
|
29
29
|
public readonly app: ReturnType<typeof createAppMethods>;
|
|
30
30
|
public readonly auth: ReturnType<typeof createAuthMethods>;
|
|
31
|
+
public readonly entities: ReturnType<typeof createEntitiesMethods>;
|
|
31
32
|
public readonly jobs: ReturnType<typeof createJobsMethods>;
|
|
32
33
|
public readonly music: ReturnType<typeof createMusicMethods>;
|
|
33
34
|
public readonly upload: ReturnType<typeof createUploadMethods>;
|
|
35
|
+
public readonly video: ReturnType<typeof createVideoMethods>;
|
|
34
36
|
|
|
35
37
|
constructor(public readonly transport: Transport) {
|
|
36
38
|
const call = this.createCallFn();
|
|
37
39
|
this.admin = createAdminMethods(call);
|
|
38
40
|
this.app = createAppMethods(call);
|
|
39
41
|
this.auth = createAuthMethods(call);
|
|
42
|
+
this.entities = createEntitiesMethods(call);
|
|
40
43
|
this.jobs = createJobsMethods(transport);
|
|
41
44
|
this.music = createMusicMethods(call);
|
|
42
45
|
this.upload = createUploadMethods(transport);
|
|
46
|
+
this.video = createVideoMethods(call);
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
// -------------------------------------------------------------------------
|
|
@@ -58,7 +62,14 @@ export class FreqholeClient {
|
|
|
58
62
|
): Promise<SafeParseResult<Resp>> => {
|
|
59
63
|
// validate request body for POST/PUT/PATCH
|
|
60
64
|
const isFormData = params instanceof FormData;
|
|
61
|
-
if (
|
|
65
|
+
if (
|
|
66
|
+
method !== "GET" &&
|
|
67
|
+
method !== "DELETE" &&
|
|
68
|
+
method !== "HEAD" &&
|
|
69
|
+
reqSchema &&
|
|
70
|
+
params &&
|
|
71
|
+
!isFormData
|
|
72
|
+
) {
|
|
62
73
|
const validated = reqSchema.safeParse(params);
|
|
63
74
|
if (!validated.success) {
|
|
64
75
|
return { success: false, error: validated.error };
|
|
@@ -75,51 +86,17 @@ export class FreqholeClient {
|
|
|
75
86
|
}
|
|
76
87
|
|
|
77
88
|
// prepare body
|
|
78
|
-
const body =
|
|
79
|
-
|
|
80
|
-
|
|
89
|
+
const body =
|
|
90
|
+
method !== "GET" && method !== "DELETE" && method !== "HEAD" && params && !isFormData
|
|
91
|
+
? JSON.stringify(params)
|
|
92
|
+
: undefined;
|
|
81
93
|
|
|
82
94
|
try {
|
|
83
95
|
const response = await this.transport.request(method, finalPath, body);
|
|
84
96
|
|
|
85
97
|
// handle errors: status >= 400 OR status 0 (IPC/network failure)
|
|
86
98
|
if (response.status >= 400 || response.status === 0) {
|
|
87
|
-
|
|
88
|
-
let errorMessage = response.status === 0 ? "connection error" : `HTTP ${response.status}`;
|
|
89
|
-
let errorCode: string | undefined;
|
|
90
|
-
try {
|
|
91
|
-
const errorBody = JSON.parse(response.body);
|
|
92
|
-
if (errorBody?.error) {
|
|
93
|
-
errorMessage = response.status === 0
|
|
94
|
-
? errorBody.error
|
|
95
|
-
: `HTTP ${response.status}: ${errorBody.error}`;
|
|
96
|
-
}
|
|
97
|
-
if (errorBody?.message) {
|
|
98
|
-
errorMessage = response.status === 0
|
|
99
|
-
? errorBody.message
|
|
100
|
-
: `HTTP ${response.status}: ${errorBody.message}`;
|
|
101
|
-
}
|
|
102
|
-
if (errorBody?.code) {
|
|
103
|
-
errorCode = errorBody.code;
|
|
104
|
-
}
|
|
105
|
-
} catch {
|
|
106
|
-
// body wasn't JSON
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const issuePath: (string | number)[] = [];
|
|
110
|
-
if (response.status === 401) {
|
|
111
|
-
issuePath.push(AUTH_ERROR_PATH);
|
|
112
|
-
}
|
|
113
|
-
if (errorCode) {
|
|
114
|
-
issuePath.push(errorCode);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
success: false,
|
|
119
|
-
error: new z.ZodError([
|
|
120
|
-
{ code: "custom", path: issuePath, message: errorMessage },
|
|
121
|
-
]),
|
|
122
|
-
};
|
|
99
|
+
return { success: false, error: toZodError(response.body, response.status) };
|
|
123
100
|
}
|
|
124
101
|
|
|
125
102
|
// no response schema (e.g., blob streaming)
|
|
@@ -132,21 +109,11 @@ export class FreqholeClient {
|
|
|
132
109
|
|
|
133
110
|
// check for GrimoireResponse failure (success: false with errors)
|
|
134
111
|
if (json.success === false) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
if (errorCode) {
|
|
142
|
-
issuePath.push(errorCode);
|
|
143
|
-
}
|
|
144
|
-
return {
|
|
145
|
-
success: false,
|
|
146
|
-
error: new z.ZodError([
|
|
147
|
-
{ code: "custom", path: issuePath, message: errorMessage },
|
|
148
|
-
]),
|
|
149
|
-
};
|
|
112
|
+
// this used to be silent - a business-logic failure (e.g. "video
|
|
113
|
+
// not found") would just surface as `null` data to the caller
|
|
114
|
+
// with no trace anywhere, making it look like a data-wiring bug.
|
|
115
|
+
console.warn(`[API] ${domain}.${routeName} failed:`, json.message, json.errors);
|
|
116
|
+
return { success: false, error: toZodError(response.body, response.status) };
|
|
150
117
|
}
|
|
151
118
|
|
|
152
119
|
const data = json.data ?? json;
|
|
@@ -176,9 +143,7 @@ export class FreqholeClient {
|
|
|
176
143
|
}
|
|
177
144
|
return {
|
|
178
145
|
success: false,
|
|
179
|
-
error: new z.ZodError([
|
|
180
|
-
{ code: "custom", path: [], message },
|
|
181
|
-
]),
|
|
146
|
+
error: new z.ZodError([{ code: "custom", path: [], message }]),
|
|
182
147
|
};
|
|
183
148
|
}
|
|
184
149
|
};
|
|
@@ -236,26 +201,24 @@ export function isAuthError<T>(result: SafeParseResult<T>): boolean {
|
|
|
236
201
|
|
|
237
202
|
export function isNetworkError<T>(result: SafeParseResult<T>): boolean {
|
|
238
203
|
if (result.success) return false;
|
|
239
|
-
return result.error.issues.some(
|
|
240
|
-
(issue)
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
);
|
|
204
|
+
return result.error.issues.some((issue) => {
|
|
205
|
+
if (issue.code !== "custom") return false;
|
|
206
|
+
const msg = issue.message.toLowerCase();
|
|
207
|
+
|
|
208
|
+
// HTTP fetch errors
|
|
209
|
+
if (msg === "failed to fetch" || msg === "network error") return true;
|
|
210
|
+
|
|
211
|
+
// P2P/iroh connection errors - be generous with matching
|
|
212
|
+
if (msg.includes("connection")) return true; // connection failed, closed, refused, etc
|
|
213
|
+
if (msg.includes("connect to peer")) return true; // "failed to connect to peer ..."
|
|
214
|
+
if (msg.includes("federation api error")) return true; // wrapper from p2p_client
|
|
215
|
+
if (msg.includes("no addressing information")) return true; // iroh: peer has no relay/direct addrs
|
|
216
|
+
if (msg.includes("timeout") || msg.includes("unreachable")) return true;
|
|
217
|
+
if (msg.includes("closed")) return true; // ClosedPath, stream closed, etc
|
|
218
|
+
if (msg.includes("no route") || msg.includes("endpoint")) return true;
|
|
219
|
+
if (msg.includes("stream") && (msg.includes("error") || msg.includes("failed"))) return true;
|
|
220
|
+
if (msg.includes("read") && msg.includes("error")) return true;
|
|
221
|
+
|
|
222
|
+
return false;
|
|
223
|
+
});
|
|
261
224
|
}
|