@fractalshq/sync 0.0.2 → 0.0.3

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.
@@ -0,0 +1,498 @@
1
+ import {
2
+ __spreadProps,
3
+ __spreadValues
4
+ } from "./chunk-I4E63NIC.mjs";
5
+
6
+ // src/server/handler/errors.ts
7
+ var SyncHandlerError = class extends Error {
8
+ constructor(status, code, message, details) {
9
+ super(message || code);
10
+ this.status = status;
11
+ this.code = code;
12
+ this.details = details;
13
+ this.name = "SyncHandlerError";
14
+ }
15
+ };
16
+
17
+ // src/server/client.ts
18
+ var SyncServerClient = class _SyncServerClient {
19
+ constructor(options) {
20
+ this.options = options;
21
+ }
22
+ static fromRequest(req, baseURL = process.env.SYNC_BASE_URL || "") {
23
+ const copyHeaders = () => {
24
+ const headers = {};
25
+ const cookie = req.headers.get("cookie");
26
+ if (cookie) headers["cookie"] = cookie;
27
+ const auth = req.headers.get("authorization");
28
+ if (auth) headers["authorization"] = auth;
29
+ const ua = req.headers.get("user-agent");
30
+ if (ua) headers["user-agent"] = ua;
31
+ const xff = req.headers.get("x-forwarded-for");
32
+ if (xff) headers["x-forwarded-for"] = xff;
33
+ return headers;
34
+ };
35
+ return new _SyncServerClient({ baseURL, getHeaders: copyHeaders });
36
+ }
37
+ static fromApiKey(apiKey, baseURL = process.env.SYNC_BASE_URL || "") {
38
+ return new _SyncServerClient({
39
+ baseURL,
40
+ getHeaders: () => apiKey ? { "x-internal-api-key": apiKey } : {}
41
+ });
42
+ }
43
+ async request(path, init) {
44
+ const headersInit = typeof this.options.getHeaders === "function" ? await this.options.getHeaders() : {};
45
+ const headers = new Headers(__spreadValues(__spreadValues({ "content-type": "application/json" }, headersInit), (init == null ? void 0 : init.headers) || {}));
46
+ const res = await fetch(`${this.options.baseURL}${path}`, __spreadProps(__spreadValues({}, init), { headers, credentials: "include" }));
47
+ if (!res.ok) {
48
+ const contentType = res.headers.get("content-type") || "";
49
+ let details = null;
50
+ if (contentType.includes("application/json")) {
51
+ details = await res.json().catch(() => null);
52
+ } else {
53
+ const text = await res.text().catch(() => "");
54
+ if (text) details = { message: text };
55
+ }
56
+ const code = typeof (details == null ? void 0 : details.error) === "string" && details.error.trim().length > 0 ? details.error : "upstream_error";
57
+ const message = typeof (details == null ? void 0 : details.message) === "string" && details.message.trim().length > 0 ? details.message : void 0;
58
+ throw new SyncHandlerError(res.status, code, message != null ? message : `Request failed: ${res.status}`, details != null ? details : void 0);
59
+ }
60
+ if (res.status === 204) return void 0;
61
+ return await res.json();
62
+ }
63
+ buildQueryString(query) {
64
+ if (!query) return "";
65
+ if (typeof query === "string") return query.startsWith("?") ? query.slice(1) : query;
66
+ if (query instanceof URLSearchParams) return query.toString();
67
+ const params = new URLSearchParams();
68
+ for (const [key, value] of Object.entries(query)) {
69
+ if (value === void 0 || value === null) continue;
70
+ params.set(key, String(value));
71
+ }
72
+ return params.toString();
73
+ }
74
+ withQuery(path, query) {
75
+ const qs = this.buildQueryString(query);
76
+ if (!qs) return path;
77
+ return `${path}?${qs}`;
78
+ }
79
+ listDistributions(params) {
80
+ const search = new URLSearchParams();
81
+ if (params == null ? void 0 : params.orgId) search.set("orgId", params.orgId);
82
+ if (params == null ? void 0 : params.status) search.set("status", params.status);
83
+ if (params == null ? void 0 : params.mint) search.set("mint", params.mint);
84
+ if (params == null ? void 0 : params.limit) search.set("limit", String(params.limit));
85
+ if (params == null ? void 0 : params.cursor) search.set("cursor", params.cursor);
86
+ const qs = search.toString();
87
+ return this.request(`/api/v1/distributions${qs ? `?${qs}` : ""}`);
88
+ }
89
+ createDistribution(body) {
90
+ return this.request(`/api/v1/distributions/create-transaction`, {
91
+ method: "POST",
92
+ body: JSON.stringify(body)
93
+ });
94
+ }
95
+ createDistributionWithId(distributionId, body) {
96
+ return this.request(
97
+ `/api/v1/distributions/${encodeURIComponent(distributionId)}/create-transaction`,
98
+ {
99
+ method: "POST",
100
+ body: JSON.stringify(body)
101
+ }
102
+ );
103
+ }
104
+ commitDistribution(distributionId, body) {
105
+ return this.request(`/api/v1/distributions/${encodeURIComponent(distributionId)}/commit`, {
106
+ method: "POST",
107
+ body: JSON.stringify(body)
108
+ });
109
+ }
110
+ buildClaimTransaction(body) {
111
+ return this.request(`/api/v1/claims/${encodeURIComponent(body.distributionId)}/create-transaction`, {
112
+ method: "POST",
113
+ body: JSON.stringify(body)
114
+ });
115
+ }
116
+ commitClaim(body) {
117
+ return this.request(`/api/v1/claims/${encodeURIComponent(body.distributionId)}/commit`, {
118
+ method: "POST",
119
+ body: JSON.stringify(body)
120
+ });
121
+ }
122
+ getClaimable() {
123
+ return this.request(`/api/test-xnet-claims`);
124
+ }
125
+ getClaimsMe(query) {
126
+ return this.request(this.withQuery(`/api/v1/claims/me`, query));
127
+ }
128
+ getDistributionsMe(query) {
129
+ return this.request(this.withQuery(`/api/v1/distributions/me`, query));
130
+ }
131
+ getDistributionMeById(distributionId) {
132
+ return this.request(`/api/v1/distributions/me/${encodeURIComponent(distributionId)}`);
133
+ }
134
+ getClaimHistory() {
135
+ return this.request(`/api/v1/claims/history`);
136
+ }
137
+ health() {
138
+ return this.request(`/api/v1/health`);
139
+ }
140
+ };
141
+
142
+ // src/server/handler/roles.ts
143
+ var ORG_DEPLOYER_ROLES = /* @__PURE__ */ new Set(["owner", "admin", "deployer"]);
144
+ var PLATFORM_DEPLOYER_ROLES = /* @__PURE__ */ new Set(["admin", "deployer"]);
145
+ function normalizeRole(role) {
146
+ if (!role) return "";
147
+ return String(role).trim().toLowerCase();
148
+ }
149
+ function resolveWalletFromMe(me) {
150
+ var _a, _b, _c, _d, _e;
151
+ if (!me || typeof me !== "object") return null;
152
+ const candidate = (me == null ? void 0 : me.wallet) || ((_a = me == null ? void 0 : me.user) == null ? void 0 : _a.wallet) || ((_b = me == null ? void 0 : me.user) == null ? void 0 : _b.pubkey) || ((_c = me == null ? void 0 : me.user) == null ? void 0 : _c.name) || ((_d = me == null ? void 0 : me.user) == null ? void 0 : _d.id) || null;
153
+ if (candidate && typeof candidate === "string" && candidate.length > 0) return candidate;
154
+ const accounts = Array.isArray(me == null ? void 0 : me.accounts) ? me.accounts : [];
155
+ const walletAccount = accounts.find((acct) => typeof (acct == null ? void 0 : acct.pubkey) === "string" && acct.pubkey.length > 0);
156
+ return (_e = walletAccount == null ? void 0 : walletAccount.pubkey) != null ? _e : null;
157
+ }
158
+ function requireLoggedIn(req) {
159
+ const auth = req.apiAuth;
160
+ if (auth && auth.ok) return auth;
161
+ throw new SyncHandlerError(401, "unauthorized");
162
+ }
163
+ function requireDeployer(req) {
164
+ const auth = requireLoggedIn(req);
165
+ if (ORG_DEPLOYER_ROLES.has(normalizeRole(auth.orgRole)) || PLATFORM_DEPLOYER_ROLES.has(normalizeRole(auth.platformRole))) {
166
+ return auth;
167
+ }
168
+ throw new SyncHandlerError(403, "forbidden_deployer_role");
169
+ }
170
+ function getSessionWallet(req) {
171
+ var _a, _b;
172
+ const direct = typeof req.wallet === "string" && req.wallet.length > 0 ? req.wallet : null;
173
+ if (direct) return direct;
174
+ const authWallet = typeof ((_a = req.apiAuth) == null ? void 0 : _a.wallet) === "string" && req.apiAuth.wallet.length > 0 ? req.apiAuth.wallet : null;
175
+ if (authWallet) return authWallet;
176
+ return resolveWalletFromMe((_b = req.apiAuth) == null ? void 0 : _b.me);
177
+ }
178
+ function requireSessionWallet(req, expected) {
179
+ const wallet = getSessionWallet(req);
180
+ if (!wallet) {
181
+ throw new SyncHandlerError(401, "unauthorized");
182
+ }
183
+ if (expected && expected.length > 0 && expected !== wallet) {
184
+ throw new SyncHandlerError(403, "forbidden_wallet_mismatch");
185
+ }
186
+ return wallet;
187
+ }
188
+
189
+ // src/server/handler/utils.ts
190
+ var BODY_SYMBOL = Symbol.for("fractals.sync.handler.body");
191
+ async function readJsonBody(req) {
192
+ const existing = req[BODY_SYMBOL];
193
+ if (existing !== void 0) return existing;
194
+ try {
195
+ const parsed = await req.json();
196
+ req[BODY_SYMBOL] = parsed;
197
+ return parsed;
198
+ } catch (error) {
199
+ throw new SyncHandlerError(400, "invalid_json", "Request body must be valid JSON");
200
+ }
201
+ }
202
+ function coerceString(value) {
203
+ if (typeof value === "string" && value.trim().length > 0) return value;
204
+ return void 0;
205
+ }
206
+ function toOptionalNumber(value) {
207
+ if (typeof value === "number" && Number.isFinite(value)) return value;
208
+ if (typeof value === "string" && value.trim().length > 0) {
209
+ const parsed = Number(value);
210
+ if (!Number.isNaN(parsed)) return parsed;
211
+ }
212
+ return void 0;
213
+ }
214
+ function decodePathSegment(value) {
215
+ if (typeof value !== "string") return "";
216
+ try {
217
+ return decodeURIComponent(value);
218
+ } catch (e) {
219
+ return value;
220
+ }
221
+ }
222
+
223
+ // src/server/handler/claims.ts
224
+ async function handleClaimsRoute(ctx) {
225
+ if (ctx.segments.length < 2) return null;
226
+ const [rawDistributionId, action] = ctx.segments;
227
+ const distributionId = decodePathSegment(rawDistributionId);
228
+ if (!distributionId) {
229
+ throw new SyncHandlerError(400, "distribution_id_required");
230
+ }
231
+ if (ctx.method !== "POST") return null;
232
+ if (action === "create-transaction") {
233
+ return handleClaimBuild(ctx, distributionId);
234
+ }
235
+ if (action === "commit") {
236
+ return handleClaimCommit(ctx, distributionId);
237
+ }
238
+ return null;
239
+ }
240
+ async function handleClaimsHistoryRoute(ctx) {
241
+ if (ctx.method !== "GET") return null;
242
+ if (ctx.segments.length > 0) return null;
243
+ requireLoggedIn(ctx.req);
244
+ const result = await ctx.client.getClaimHistory();
245
+ return Response.json(result);
246
+ }
247
+ async function handleClaimBuild(ctx, distributionId) {
248
+ const raw = await readJsonBody(ctx.req);
249
+ const body = __spreadValues({}, raw);
250
+ const explicitWallet = coerceString(body.claimant) || coerceString(body.claimantPubkey) || coerceString(body.wallet);
251
+ const claimant = requireSessionWallet(ctx.req, explicitWallet);
252
+ body.distributionId = distributionId;
253
+ body.claimant = claimant;
254
+ const normalizedIndex = toOptionalNumber(raw.index);
255
+ if (typeof normalizedIndex === "number") body.index = normalizedIndex;
256
+ const normalizedUnlocked = toOptionalNumber(raw.unlockedAmount);
257
+ if (typeof normalizedUnlocked === "number") body.unlockedAmount = normalizedUnlocked;
258
+ const normalizedLocked = toOptionalNumber(raw.lockedAmount);
259
+ if (typeof normalizedLocked === "number") body.lockedAmount = normalizedLocked;
260
+ if (!body.distributorPda) {
261
+ const distributorPda = coerceString(raw.distributorPDA);
262
+ if (distributorPda) body.distributorPda = distributorPda;
263
+ }
264
+ const result = await ctx.client.buildClaimTransaction(body);
265
+ return Response.json(result);
266
+ }
267
+ async function handleClaimCommit(ctx, distributionId) {
268
+ var _a, _b, _c;
269
+ const body = await readJsonBody(ctx.req);
270
+ const explicitWallet = coerceString(body.claimantPubkey) || coerceString(body.claimant) || coerceString(body.wallet);
271
+ const claimant = requireSessionWallet(ctx.req, explicitWallet);
272
+ const payload = __spreadValues({}, body);
273
+ payload.distributionId = distributionId;
274
+ payload.claimantPubkey = claimant;
275
+ const signature = (_a = coerceString(payload.signature)) != null ? _a : coerceString(body.signature);
276
+ if (signature) payload.signature = signature;
277
+ const signedTransactionBase64 = coerceString(body.signedTransactionBase64);
278
+ const transaction = (_c = (_b = coerceString(payload.transaction)) != null ? _b : coerceString(body.transaction)) != null ? _c : signedTransactionBase64;
279
+ if (transaction) payload.transaction = transaction;
280
+ if (signedTransactionBase64) payload.signedTransactionBase64 = signedTransactionBase64;
281
+ const hasSignature = typeof payload.signature === "string" && payload.signature.length > 0;
282
+ const hasTxn = typeof payload.transaction === "string" && payload.transaction.length > 0;
283
+ const hasTxnBase64 = typeof payload.signedTransactionBase64 === "string" && payload.signedTransactionBase64.length > 0;
284
+ if (!hasSignature && !hasTxn && !hasTxnBase64) {
285
+ throw new SyncHandlerError(400, "transaction_or_signature_required");
286
+ }
287
+ const result = await ctx.client.commitClaim(payload);
288
+ return Response.json(result);
289
+ }
290
+
291
+ // src/server/handler/distributions.ts
292
+ async function handleDistributionsRoute(ctx) {
293
+ if (ctx.segments.length === 0) return null;
294
+ const [first, second] = ctx.segments;
295
+ if (ctx.method === "POST" && first === "create-transaction" && !second) {
296
+ requireDeployer(ctx.req);
297
+ const body = await readJsonBody(ctx.req);
298
+ const result = await ctx.client.createDistribution(body);
299
+ return Response.json(result);
300
+ }
301
+ const distributionId = decodePathSegment(first);
302
+ if (!distributionId) {
303
+ throw new SyncHandlerError(400, "distribution_id_required");
304
+ }
305
+ if (ctx.method === "POST" && second === "create-transaction") {
306
+ requireDeployer(ctx.req);
307
+ const body = await readJsonBody(ctx.req);
308
+ const result = await ctx.client.createDistributionWithId(distributionId, body);
309
+ return Response.json(result);
310
+ }
311
+ if (ctx.method === "POST" && second === "commit") {
312
+ requireDeployer(ctx.req);
313
+ const body = await readJsonBody(ctx.req);
314
+ const result = await ctx.client.commitDistribution(distributionId, body);
315
+ return Response.json(result);
316
+ }
317
+ return null;
318
+ }
319
+
320
+ // src/server/handler/me.ts
321
+ async function handleClaimsMeRoute(ctx) {
322
+ var _a, _b;
323
+ if (ctx.method !== "GET") return null;
324
+ if (ctx.segments.length > 0) return null;
325
+ requireLoggedIn(ctx.req);
326
+ const result = await ctx.client.getClaimsMe((_b = (_a = ctx.url) == null ? void 0 : _a.searchParams) != null ? _b : void 0);
327
+ return Response.json(result);
328
+ }
329
+ async function handleDistributionsMeRoute(ctx) {
330
+ var _a, _b;
331
+ if (ctx.method !== "GET") return null;
332
+ requireLoggedIn(ctx.req);
333
+ if (ctx.segments.length === 0) {
334
+ const result = await ctx.client.getDistributionsMe((_b = (_a = ctx.url) == null ? void 0 : _a.searchParams) != null ? _b : void 0);
335
+ return Response.json(result);
336
+ }
337
+ if (ctx.segments.length === 1) {
338
+ const distributionId = decodePathSegment(ctx.segments[0]);
339
+ const result = await ctx.client.getDistributionMeById(distributionId);
340
+ return Response.json(result);
341
+ }
342
+ return null;
343
+ }
344
+
345
+ // src/server/handler.ts
346
+ var ALLOWED_METHODS = /* @__PURE__ */ new Set(["GET", "POST"]);
347
+ var ALLOW_HEADER = "GET, POST";
348
+ async function syncRouteHandler(req, context = {}, options = {}) {
349
+ var _a;
350
+ const method = (req.method || "GET").toUpperCase();
351
+ if (!ALLOWED_METHODS.has(method)) {
352
+ return new Response(JSON.stringify({ error: "method_not_allowed" }), {
353
+ status: 405,
354
+ headers: { "content-type": "application/json", allow: ALLOW_HEADER }
355
+ });
356
+ }
357
+ const url = resolveUrl(req);
358
+ const segments = await resolveSegments(req, context, url);
359
+ if (segments.length === 0) {
360
+ return notFound();
361
+ }
362
+ const logger = (_a = options.logger) != null ? _a : console;
363
+ let client;
364
+ try {
365
+ client = resolveClient(req, options);
366
+ } catch (error) {
367
+ return handleError(error, req, options, logger);
368
+ }
369
+ const [resource, ...rest] = segments;
370
+ const baseContext = { req, client, method, url, segments: rest };
371
+ try {
372
+ let response = null;
373
+ if (resource === "claims") {
374
+ if (rest[0] === "me") {
375
+ response = await handleClaimsMeRoute(__spreadProps(__spreadValues({}, baseContext), { segments: rest.slice(1) }));
376
+ } else if (rest[0] === "history") {
377
+ response = await handleClaimsHistoryRoute(__spreadProps(__spreadValues({}, baseContext), { segments: rest.slice(1) }));
378
+ } else {
379
+ response = await handleClaimsRoute(baseContext);
380
+ }
381
+ } else if (resource === "distributions") {
382
+ if (rest[0] === "me") {
383
+ response = await handleDistributionsMeRoute(__spreadProps(__spreadValues({}, baseContext), { segments: rest.slice(1) }));
384
+ } else {
385
+ response = await handleDistributionsRoute(baseContext);
386
+ }
387
+ }
388
+ if (response) return response;
389
+ return notFound();
390
+ } catch (error) {
391
+ return handleError(error, req, options, logger);
392
+ }
393
+ }
394
+ function createMethodHandler(method) {
395
+ return (req, context, options) => {
396
+ const incoming = (req.method || "").toUpperCase();
397
+ if (incoming && incoming !== method) {
398
+ return new Response(JSON.stringify({ error: "method_not_allowed" }), {
399
+ status: 405,
400
+ headers: { "content-type": "application/json", allow: method }
401
+ });
402
+ }
403
+ return syncRouteHandler(req, context, options);
404
+ };
405
+ }
406
+ var GET = createMethodHandler("GET");
407
+ var POST = createMethodHandler("POST");
408
+ function notFound() {
409
+ return Response.json({ error: "not_found" }, { status: 404 });
410
+ }
411
+ function resolveClient(req, options) {
412
+ var _a, _b, _c;
413
+ if (options.client) return options.client;
414
+ const baseURL = (_c = (_b = (_a = options.baseURL) != null ? _a : process.env.SYNC_BASE_URL) != null ? _b : process.env.NEXT_PUBLIC_SYNC_BASE_URL) != null ? _c : "";
415
+ if (!baseURL) {
416
+ throw new SyncHandlerError(500, "sync_base_url_missing", "SYNC_BASE_URL is not configured");
417
+ }
418
+ if (options.getHeadersFromRequest) {
419
+ return new SyncServerClient({
420
+ baseURL,
421
+ getHeaders: () => options.getHeadersFromRequest(req)
422
+ });
423
+ }
424
+ return SyncServerClient.fromRequest(req, baseURL);
425
+ }
426
+ async function resolveSegments(req, context, url) {
427
+ var _a;
428
+ const params = await resolveParams(context == null ? void 0 : context.params);
429
+ if (params == null ? void 0 : params.fractalsSync) {
430
+ return sanitizeSegments(params.fractalsSync);
431
+ }
432
+ const pathname = (_a = url == null ? void 0 : url.pathname) != null ? _a : typeof req.url === "string" ? safePathname(req.url) : "";
433
+ if (!pathname) return [];
434
+ const parts = pathname.split("/").filter(Boolean);
435
+ if (parts.length === 0) return [];
436
+ const catchAllIndex = parts.findIndex((part) => part === "fractals-sync" || part === "[...fractals-sync]");
437
+ if (catchAllIndex >= 0) {
438
+ return sanitizeSegments(parts.slice(catchAllIndex + 1));
439
+ }
440
+ return [];
441
+ }
442
+ async function resolveParams(params) {
443
+ var _a;
444
+ if (!params) return null;
445
+ if (typeof (params == null ? void 0 : params.then) === "function") {
446
+ return (_a = await params) != null ? _a : null;
447
+ }
448
+ return params != null ? params : null;
449
+ }
450
+ function sanitizeSegments(segments) {
451
+ return segments.map((segment) => decodePathSegment(String(segment || ""))).filter((segment) => segment.length > 0);
452
+ }
453
+ function resolveUrl(req) {
454
+ if (req.nextUrl instanceof URL) {
455
+ return req.nextUrl;
456
+ }
457
+ const rawUrl = typeof req.url === "string" ? req.url : null;
458
+ if (!rawUrl) return null;
459
+ try {
460
+ return new URL(rawUrl, "http://localhost");
461
+ } catch (e) {
462
+ return null;
463
+ }
464
+ }
465
+ function safePathname(url) {
466
+ try {
467
+ return new URL(url, "http://localhost").pathname;
468
+ } catch (e) {
469
+ return "";
470
+ }
471
+ }
472
+ async function handleError(error, req, options, logger) {
473
+ var _a, _b;
474
+ if (error instanceof SyncHandlerError) {
475
+ if (error.code === "unauthorized" && options.onUnauthorized) {
476
+ try {
477
+ const custom = await options.onUnauthorized(req);
478
+ if (custom) return custom;
479
+ } catch (hookError) {
480
+ (_a = logger == null ? void 0 : logger.warn) == null ? void 0 : _a.call(logger, "[syncRouteHandler] onUnauthorized hook failed", hookError);
481
+ }
482
+ }
483
+ const payload = { error: error.code };
484
+ if (error.message && error.message !== error.code) payload.message = error.message;
485
+ if (error.details !== void 0) payload.details = error.details;
486
+ return Response.json(payload, { status: error.status });
487
+ }
488
+ (_b = logger == null ? void 0 : logger.error) == null ? void 0 : _b.call(logger, "[syncRouteHandler] unhandled_error", error);
489
+ return Response.json({ error: "sync_handler_error" }, { status: 500 });
490
+ }
491
+
492
+ export {
493
+ SyncServerClient,
494
+ syncRouteHandler,
495
+ createMethodHandler,
496
+ GET,
497
+ POST
498
+ };
package/dist/index.js CHANGED
@@ -51,6 +51,17 @@ __export(index_exports, {
51
51
  });
52
52
  module.exports = __toCommonJS(index_exports);
53
53
 
54
+ // src/server/handler/errors.ts
55
+ var SyncHandlerError = class extends Error {
56
+ constructor(status, code, message, details) {
57
+ super(message || code);
58
+ this.status = status;
59
+ this.code = code;
60
+ this.details = details;
61
+ this.name = "SyncHandlerError";
62
+ }
63
+ };
64
+
54
65
  // src/server/client.ts
55
66
  var SyncServerClient = class _SyncServerClient {
56
67
  constructor(options) {
@@ -82,8 +93,17 @@ var SyncServerClient = class _SyncServerClient {
82
93
  const headers = new Headers(__spreadValues(__spreadValues({ "content-type": "application/json" }, headersInit), (init == null ? void 0 : init.headers) || {}));
83
94
  const res = await fetch(`${this.options.baseURL}${path}`, __spreadProps(__spreadValues({}, init), { headers, credentials: "include" }));
84
95
  if (!res.ok) {
85
- const text = await res.text().catch(() => "");
86
- throw new Error(text || `Request failed: ${res.status}`);
96
+ const contentType = res.headers.get("content-type") || "";
97
+ let details = null;
98
+ if (contentType.includes("application/json")) {
99
+ details = await res.json().catch(() => null);
100
+ } else {
101
+ const text = await res.text().catch(() => "");
102
+ if (text) details = { message: text };
103
+ }
104
+ const code = typeof (details == null ? void 0 : details.error) === "string" && details.error.trim().length > 0 ? details.error : "upstream_error";
105
+ const message = typeof (details == null ? void 0 : details.message) === "string" && details.message.trim().length > 0 ? details.message : void 0;
106
+ throw new SyncHandlerError(res.status, code, message != null ? message : `Request failed: ${res.status}`, details != null ? details : void 0);
87
107
  }
88
108
  if (res.status === 204) return void 0;
89
109
  return await res.json();
@@ -167,17 +187,6 @@ var SyncServerClient = class _SyncServerClient {
167
187
  }
168
188
  };
169
189
 
170
- // src/server/handler/errors.ts
171
- var SyncHandlerError = class extends Error {
172
- constructor(status, code, message, details) {
173
- super(message || code);
174
- this.status = status;
175
- this.code = code;
176
- this.details = details;
177
- this.name = "SyncHandlerError";
178
- }
179
- };
180
-
181
190
  // src/server/handler/roles.ts
182
191
  var ORG_DEPLOYER_ROLES = /* @__PURE__ */ new Set(["owner", "admin", "deployer"]);
183
192
  var PLATFORM_DEPLOYER_ROLES = /* @__PURE__ */ new Set(["admin", "deployer"]);
@@ -276,6 +285,13 @@ async function handleClaimsRoute(ctx) {
276
285
  }
277
286
  return null;
278
287
  }
288
+ async function handleClaimsHistoryRoute(ctx) {
289
+ if (ctx.method !== "GET") return null;
290
+ if (ctx.segments.length > 0) return null;
291
+ requireLoggedIn(ctx.req);
292
+ const result = await ctx.client.getClaimHistory();
293
+ return Response.json(result);
294
+ }
279
295
  async function handleClaimBuild(ctx, distributionId) {
280
296
  const raw = await readJsonBody(ctx.req);
281
297
  const body = __spreadValues({}, raw);
@@ -405,6 +421,8 @@ async function syncRouteHandler(req, context = {}, options = {}) {
405
421
  if (resource === "claims") {
406
422
  if (rest[0] === "me") {
407
423
  response = await handleClaimsMeRoute(__spreadProps(__spreadValues({}, baseContext), { segments: rest.slice(1) }));
424
+ } else if (rest[0] === "history") {
425
+ response = await handleClaimsHistoryRoute(__spreadProps(__spreadValues({}, baseContext), { segments: rest.slice(1) }));
408
426
  } else {
409
427
  response = await handleClaimsRoute(baseContext);
410
428
  }
package/dist/index.mjs CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  SyncServerClient,
13
13
  createMethodHandler,
14
14
  syncRouteHandler
15
- } from "./chunk-N3276DIZ.mjs";
15
+ } from "./chunk-BHPW43XZ.mjs";
16
16
  import "./chunk-I4E63NIC.mjs";
17
17
  export {
18
18
  DEFAULT_RPC_ENDPOINT,
@@ -45,6 +45,17 @@ __export(server_exports, {
45
45
  });
46
46
  module.exports = __toCommonJS(server_exports);
47
47
 
48
+ // src/server/handler/errors.ts
49
+ var SyncHandlerError = class extends Error {
50
+ constructor(status, code, message, details) {
51
+ super(message || code);
52
+ this.status = status;
53
+ this.code = code;
54
+ this.details = details;
55
+ this.name = "SyncHandlerError";
56
+ }
57
+ };
58
+
48
59
  // src/server/client.ts
49
60
  var SyncServerClient = class _SyncServerClient {
50
61
  constructor(options) {
@@ -76,8 +87,17 @@ var SyncServerClient = class _SyncServerClient {
76
87
  const headers = new Headers(__spreadValues(__spreadValues({ "content-type": "application/json" }, headersInit), (init == null ? void 0 : init.headers) || {}));
77
88
  const res = await fetch(`${this.options.baseURL}${path}`, __spreadProps(__spreadValues({}, init), { headers, credentials: "include" }));
78
89
  if (!res.ok) {
79
- const text = await res.text().catch(() => "");
80
- throw new Error(text || `Request failed: ${res.status}`);
90
+ const contentType = res.headers.get("content-type") || "";
91
+ let details = null;
92
+ if (contentType.includes("application/json")) {
93
+ details = await res.json().catch(() => null);
94
+ } else {
95
+ const text = await res.text().catch(() => "");
96
+ if (text) details = { message: text };
97
+ }
98
+ const code = typeof (details == null ? void 0 : details.error) === "string" && details.error.trim().length > 0 ? details.error : "upstream_error";
99
+ const message = typeof (details == null ? void 0 : details.message) === "string" && details.message.trim().length > 0 ? details.message : void 0;
100
+ throw new SyncHandlerError(res.status, code, message != null ? message : `Request failed: ${res.status}`, details != null ? details : void 0);
81
101
  }
82
102
  if (res.status === 204) return void 0;
83
103
  return await res.json();
@@ -161,17 +181,6 @@ var SyncServerClient = class _SyncServerClient {
161
181
  }
162
182
  };
163
183
 
164
- // src/server/handler/errors.ts
165
- var SyncHandlerError = class extends Error {
166
- constructor(status, code, message, details) {
167
- super(message || code);
168
- this.status = status;
169
- this.code = code;
170
- this.details = details;
171
- this.name = "SyncHandlerError";
172
- }
173
- };
174
-
175
184
  // src/server/handler/roles.ts
176
185
  var ORG_DEPLOYER_ROLES = /* @__PURE__ */ new Set(["owner", "admin", "deployer"]);
177
186
  var PLATFORM_DEPLOYER_ROLES = /* @__PURE__ */ new Set(["admin", "deployer"]);
@@ -270,6 +279,13 @@ async function handleClaimsRoute(ctx) {
270
279
  }
271
280
  return null;
272
281
  }
282
+ async function handleClaimsHistoryRoute(ctx) {
283
+ if (ctx.method !== "GET") return null;
284
+ if (ctx.segments.length > 0) return null;
285
+ requireLoggedIn(ctx.req);
286
+ const result = await ctx.client.getClaimHistory();
287
+ return Response.json(result);
288
+ }
273
289
  async function handleClaimBuild(ctx, distributionId) {
274
290
  const raw = await readJsonBody(ctx.req);
275
291
  const body = __spreadValues({}, raw);
@@ -399,6 +415,8 @@ async function syncRouteHandler(req, context = {}, options = {}) {
399
415
  if (resource === "claims") {
400
416
  if (rest[0] === "me") {
401
417
  response = await handleClaimsMeRoute(__spreadProps(__spreadValues({}, baseContext), { segments: rest.slice(1) }));
418
+ } else if (rest[0] === "history") {
419
+ response = await handleClaimsHistoryRoute(__spreadProps(__spreadValues({}, baseContext), { segments: rest.slice(1) }));
402
420
  } else {
403
421
  response = await handleClaimsRoute(baseContext);
404
422
  }
@@ -4,7 +4,7 @@ import {
4
4
  SyncServerClient,
5
5
  createMethodHandler,
6
6
  syncRouteHandler
7
- } from "../chunk-N3276DIZ.mjs";
7
+ } from "../chunk-BHPW43XZ.mjs";
8
8
  import "../chunk-I4E63NIC.mjs";
9
9
  export {
10
10
  GET,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fractalshq/sync",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Fractals Sync SDK: shared types, server client, React hooks, and widgets",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",