-
Notifications
You must be signed in to change notification settings - Fork 23
feat: show remaining CDN & cache-miss egress #538
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
synapse-dev | 5905842 | Commit Preview URL Branch Preview URL |
Jan 20 2026, 01:06 PM |
apps/synapse-playground/src/components/warm-storage/data-sets-section.tsx
Outdated
Show resolved
Hide resolved
Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
|
This seems fine to me and I appreciate the AGENTS.md edits but I'm going to leave it to @hugomrdias to review |
- Change format to "Egress remaining: X GiB delivery · Y GiB cache-miss" - Remove FolderSync icon, keep Globe icon with tooltip "This data set is using CDN" - Use binary units (KiB, MiB, GiB, TiB) instead of decimal units Co-Authored-By: Claude Code <noreply@anthropic.com> Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
|
I addressed the review comments, this PR is ready for another round of reviews. 🙏🏻 |
Add unit tests for getStatsBaseUrl and validateStatsResponse functions. Export validateStatsResponse to enable direct testing without HTTP mocking. Also update AGENTS.md with testing guidelines: - Use assert.deepStrictEqual for object comparisons - Use parameterized tests with descriptive names Co-Authored-By: Claude Code <noreply@anthropic.com> Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
just in case Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
|
@hugomrdias I addressed your comments, PTAL again. |
apps/synapse-playground/src/components/warm-storage/data-sets-section.tsx
Outdated
Show resolved
Hide resolved
Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
apps/synapse-playground/src/components/warm-storage/data-sets-section.tsx
Outdated
Show resolved
Hide resolved
| function parseBigInt(value: string, fieldName: string): bigint { | ||
| // Check if the string is a valid integer format (optional minus sign followed by digits) | ||
| if (!/^-?\d+$/.test(value)) { | ||
| throw new GetDataSetStatsError('Invalid response format', `${fieldName} is not a valid integer: "${value}"`) | ||
| } | ||
| return BigInt(value) | ||
| } | ||
|
|
||
| /** | ||
| * Validates the response from FilBeam stats API and returns DataSetStats | ||
| */ | ||
| export function validateStatsResponse(data: unknown): DataSetStats { | ||
| if (typeof data !== 'object' || data === null) { | ||
| throw new GetDataSetStatsError('Invalid response format', 'Response is not an object') | ||
| } | ||
|
|
||
| const response = data as Record<string, unknown> | ||
|
|
||
| if (typeof response.cdnEgressQuota !== 'string') { | ||
| throw new GetDataSetStatsError('Invalid response format', 'cdnEgressQuota must be a string') | ||
| } | ||
|
|
||
| if (typeof response.cacheMissEgressQuota !== 'string') { | ||
| throw new GetDataSetStatsError('Invalid response format', 'cacheMissEgressQuota must be a string') | ||
| } | ||
|
|
||
| return { | ||
| cdnEgressQuota: parseBigInt(response.cdnEgressQuota, 'cdnEgressQuota'), | ||
| cacheMissEgressQuota: parseBigInt(response.cacheMissEgressQuota, 'cacheMissEgressQuota'), | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not a fan of validating http apis responses
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I guess it's okay to not validate HTTP API responses in browsers, where it's easy to reload the page on errors. Since synapse-core is targeting both browsers and long-living Node.js processes, I feel there is enough value in validating API responses.
How do @rvagg and @juliangruber feel about this?
I don't have a strong opinion. If there is a consensus to drop the validation step, then I will change the response handling in getDataSetStats() to this:
return {
cdnEgressQuota: BigInt(response.result.cdnEgressQuota),
cacheMissEgressQuota: BigInt(response.result.cacheMissEgressQuota),
}The caveat is that both BigInt(undefined) and BigInt('foo') throw a SyntaxError error. If the FilBeam Stats API ever returns an invalid response, the user will see a cryptic error Uncaught TypeError: Cannot convert undefined to a BigInt, and the only reference to FilBeam Stats will be in the stack trace. I suppose that's not entirely bad 🤷🏻
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| const response = await request.json.get<unknown>(url) | ||
|
|
||
| if (response.error) { | ||
| if (HttpError.is(response.error)) { | ||
| const status = response.error.response.status | ||
| if (status === 404) { | ||
| throw new GetDataSetStatsError(`Data set not found: ${options.dataSetId}`) | ||
| } | ||
| const errorText = await response.error.response.text().catch(() => 'Unknown error') | ||
| throw new GetDataSetStatsError( | ||
| `Failed to fetch data set stats`, | ||
| `HTTP ${status} ${response.error.response.statusText}: ${errorText}` | ||
| ) | ||
| } | ||
| throw response.error | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if your api is not JSON everywhere, dont use request.json just request.get
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see your point.
The thing is, even if our service always returns JSON everywhere, I don't know if other components like Cloudflare proxy and any proxy the user may have configured are also always returning JSON responses on unexpected errors.
I don't have a strong opinion, this code was generated by Claude Code, I am happy to adjust it to make it consistent with the rest of the codebase.
How do you suggest handling response.error in this function? Should I just wrap it in new GetDataSetStatsError? Would you like to preserve the special handling of 404?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in this file either import from core or remove completely because its already available in core
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree it will be best to share the same underlying implementation.
The difficulty: synapse-sdk and synapse-core use different API for making HTTP requests and different error classes.
Let me try to find a solution that preserves the existing synapse-sdk API and uses synapse-core under the hood.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we are changing the sdk to reuse core as much as possible, sdk will use http and errors from core very soon
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The lens I'm trying to use for core vs sdk is:
- core is a stateless functional library with pure functions.
- sdk is stateful service classes, DX-focused particularly on the golden-path (don't make me care about your junk).
apps/synapse-playground/src/components/warm-storage/data-sets-section.tsx
Outdated
Show resolved
Hide resolved
Move CDN info (Globe icon + tooltip + egress quota display) into a dedicated CdnDetails component, simplifying data-sets-section.tsx. Co-Authored-By: Claude Code <noreply@anthropic.com> Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
Rename query key from `synapse-filbeam-egress-quota` to `synapse-filbeam-egress-quotas` for consistency with other query keys that use plural when the response contains multiple values. Co-Authored-By: Claude Code <noreply@anthropic.com> Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
…apse-core Simplify FilBeamService by delegating getDataSetStats to synapse-core's implementation, eliminating code duplication. Add requestGetJson option to synapse-core for testability. Update SynapseError to prioritize explicit details over cause.message. Co-Authored-By: Claude Code <noreply@anthropic.com> Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
Move detailed HTTP error handling and fetch tests from synapse-sdk to synapse-core, keeping only smoke tests in synapse-sdk to verify the delegation works correctly. Co-Authored-By: Claude Code <noreply@anthropic.com> Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
Move helper functions (getStatsBaseUrl, parseBigInt, validateStatsResponse) after the main getDataSetStats function for better code organization. Co-Authored-By: Claude Code <noreply@anthropic.com> Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
| const response = await requestGetJson<unknown>(url) | ||
|
|
||
| if (response.error) { | ||
| if (HttpError.is(response.error)) { | ||
| const status = response.error.response.status | ||
| if (status === 404) { | ||
| throw new GetDataSetStatsError(`Data set not found: ${options.dataSetId}`, { | ||
| cause: response.error, | ||
| }) | ||
| } | ||
| const errorText = await response.error.response.text().catch(() => 'Unknown error') | ||
| throw new GetDataSetStatsError(`Failed to fetch data set stats`, { | ||
| details: `HTTP ${status} ${response.error.response.statusText}: ${errorText}`, | ||
| cause: response.error, | ||
| }) | ||
| } | ||
| throw new GetDataSetStatsError('Unexpected error', { cause: response.error }) | ||
| } | ||
|
|
||
| return validateStatsResponse(response.result) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Following up on #538 (comment) and #538 (comment).
If we apply both suggestions, then we can simplify this block as follows - is that what we want?
const response = await requestGetJson<unknown>(url)
if (response.error) {
throw new GetDataSetStatsError(
'Cannot get DataSet stats from FilBeam API.',
{ cause: response.error }
)
}
return {
cdnEgressQuota: BigInt(response.result.cdnEgressQuota),
cacheMissEgressQuota: BigInt(response.result.cacheMissEgressQuota),
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds the ability to display remaining CDN and cache-miss egress quotas for FilBeam-enabled data sets in the demo application. The implementation refactors FilBeam stats functionality from synapse-sdk into a new synapse-core module that can be shared between synapse-sdk and synapse-react packages.
Changes:
- Moved FilBeam stats API logic from synapse-sdk to synapse-core with improved error handling and validation
- Added a new React hook
useEgressQuotain synapse-react for fetching egress quota data - Updated the playground app to display remaining egress allowances for data sets with FilBeam enabled
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/synapse-core/src/filbeam/stats.ts | New core implementation for fetching and validating FilBeam data set stats |
| packages/synapse-core/src/errors/filbeam.ts | New error class for FilBeam stats operations |
| packages/synapse-core/test/filbeam.test.ts | Comprehensive tests for FilBeam stats functionality |
| packages/synapse-core/src/filbeam/index.ts | Export module for FilBeam functionality |
| packages/synapse-core/src/errors/index.ts | Added export for FilBeam errors |
| packages/synapse-core/src/errors/base.ts | Improved error message handling to prioritize explicit details |
| packages/synapse-core/package.json | Added exports for filbeam module |
| packages/synapse-sdk/src/filbeam/service.ts | Refactored to delegate to synapse-core implementation |
| packages/synapse-sdk/src/test/filbeam-service.test.ts | Updated to smoke tests that verify delegation to synapse-core |
| packages/synapse-react/src/warm-storage/use-egress-quota.ts | New React hook for querying egress quotas |
| packages/synapse-react/src/warm-storage/index.ts | Added export for useEgressQuota hook |
| apps/synapse-playground/src/components/warm-storage/cdn-details.tsx | New component to display egress quota information |
| apps/synapse-playground/src/components/warm-storage/data-sets-section.tsx | Updated to use CdnDetails component |
| apps/synapse-playground/src/lib/utils.ts | Added formatBytes utility function |
| AGENTS.md | Added documentation about monorepo structure and testing practices |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export function getStatsBaseUrl(chainId: number): string { | ||
| return chainId === 314 ? 'https://stats.filbeam.com' : 'https://calibration.stats.filbeam.com' |
Copilot
AI
Jan 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The getStatsBaseUrl function defaults all unknown chain IDs to the calibration URL. When devnet (31415926) is passed, it will incorrectly use the calibration URL instead of a devnet-specific URL or throwing an error. This could lead to silent failures when using devnet.
|
|
||
| describe('FilBeamService', () => { | ||
| describe('network type validation', () => { | ||
| it('should throw error if network type not mainnet or calibration', () => { |
Copilot
AI
Jan 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test description states "should throw error if network type not mainnet or calibration" but the FilBeamService now supports 'devnet' as well (as shown in service.ts line 51). The test description should be updated to reflect this, or a test for devnet support should be added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a great call-out. FilBeam does not support "devnet". What should the function getDataSetStats() do in such a case?
- We can throw an error for unsupported networks.
- We can return
0remaining egress (which is kind of true?) This will work well in the UI. - We can return
undefinedremaining egress to indicate that FilBeam does not support that network. The UI can handle this case and displayn/a:Egress remaining: n/a delivery · n/a cache-miss
I propose keeping this simple and throwing an error for unsupported networks like "devnet".
Thoughts?
| } | ||
|
|
||
| export interface GetDataSetStatsOptions { | ||
| /** The chain ID (314 for mainnet, 314159 for calibration) */ |
Copilot
AI
Jan 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation comment lists only chain IDs 314 and 314159, but the codebase supports devnet (31415926) as well. The comment should include all supported chain IDs for completeness.
| /** The chain ID (314 for mainnet, 314159 for calibration) */ | |
| /** The chain ID (314 for mainnet, 314159 for calibration, 31415926 for devnet) */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not a bad suggestion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a great suggestion! But: FilBeam does not support devnet 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think perhaps the whole style of this file is goiong to need to change to adapt to the style in #555 and #566 which also includes chain definition stuff, check out #566 in particular for the new synapse-core method replacements and the way @hugomrdias is doing namespaces and function arguments
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Miroslav Bajtoš <oss@bajtos.net>
Co-authored-by: Rod Vagg <rod@vagg.org>
Co-authored-by: Rod Vagg <rod@vagg.org>
Update the demo app to show remaining egress allowances for each data set where the FilBeam service is enabled.
Screenshot showing the new version in practice:
version 2 using FolderSync icon
version 1 using RefreshCw icon