Skip to content

[feat] data layer#16801

Merged
pettinarip merged 16 commits into
devfrom
datalayer
Dec 18, 2025
Merged

[feat] data layer#16801
pettinarip merged 16 commits into
devfrom
datalayer

Conversation

@corwintines
Copy link
Copy Markdown
Member

Description

Implements:

  • Creates abstract setter and getter functions for data layer
  • implements netlify blobs as storage
  • hourly and daily trigger.dev tasks
  • External fetches

@corwintines corwintines marked this pull request as draft December 4, 2025 23:26
@github-actions github-actions Bot added the dependencies 📦 Changes related to project dependencies label Dec 4, 2025
@netlify
Copy link
Copy Markdown

netlify Bot commented Dec 4, 2025

Deploy Preview for ethereumorg ready!

Name Link
🔨 Latest commit 9aec6da
🔍 Latest deploy log https://app.netlify.com/projects/ethereumorg/deploys/6944445df9179500086ef2cf
😎 Deploy Preview https://deploy-preview-16801--ethereumorg.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
7 paths audited
Performance: 57 (no change from production)
Accessibility: 94 (no change from production)
Best Practices: 100 (no change from production)
SEO: 100 (no change from production)
PWA: 59 (no change from production)
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions Bot added the tooling 🔧 Changes related to tooling of the project label Dec 5, 2025
@corwintines corwintines marked this pull request as ready for review December 5, 2025 00:34
Copy link
Copy Markdown
Member

@pettinarip pettinarip left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@corwintines this is looking really good.

I have one last architectural concern about how consumers will interact with the data-layer.

Right now, to get data you'd need to do this:

import { getData } from "@/data-layer/storage/getter"
import { FETCH_ETH_PRICE_TASK_ID } from "@/data-layer/api/fetchEthPrice"

This leaks internal details (storage paths, task IDs) into app code and loses type safety.

Suggested approach

  1. Data-layer exports a clean public API (src/data-layer/index.ts)
export async function getEthPrice(): Promise<EthPriceData | null>
export async function getL2beatData(): Promise<L2beatData | null>
// ...more

No Next.js imports here — keeps it framework agnostic so we can extract it later if needed.

  1. Next.js adapter handles caching (src/lib/data/index.ts)
import { unstable_cache } from "next/cache"
import * as dataLayer from "@/data-layer"

export const getEthPrice = unstable_cache(
  () => dataLayer.getEthPrice(),
  ["eth-price"],
  { revalidate: 3600 }
)
  1. App code uses the adapter
import { getEthPrice } from "@/lib/data"
const price = await getEthPrice() // cached + typed ✨

Why

  • Types flow automatically (no generics)
  • Data-layer stays isolated, no framework deps
  • Caching is the app's concern, not the data-layer's
  • Easy to extract to its own service later

Thoughts?

Comment thread src/data-layer/mocks/README.md Outdated
Comment on lines +17 to +31
## Files

- `index.ts` - TypeScript index file with task ID exports
- `*.json` - Individual mock data files for each task

## Last Generated

2025-12-05T21:54:56.350Z

## Statistics

- Total tasks: 21
- Successfully generated: 20
- Not found: 1

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't look relevant to consumers

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be useful depending on if we need to keep track of the last time a mocks were created, but will remove for now.

Comment thread src/data-layer/storage/netlifyBlobs.ts Outdated
let store: ReturnType<typeof getStore> | null = null

function getBlobStore() {
if (!store) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use early returns to make this more readable

if (store) {
  return store
}

// ... rest

Comment thread src/data-layer/storage/netlifyBlobs.ts Outdated
} else {
// Try automatic configuration (works in Netlify environment)
// This will use NETLIFY_BLOBS_CONTEXT if available
store = getStore("data-layer")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we are ever going to use this use case. Is this something you have in mind for other uses?

IMO we should always have a these vars. If not, throw to let us know something is not working.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For consistency and clarity, following the mockStorage name, we should call this netlifyBlobsStorage.

Comment thread src/data-layer/storage/netlifyBlobs.ts Outdated
Comment on lines +45 to +46
export const netlifyBlobsStorage: StorageImplementation &
GetStorageImplementation = {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

current typing around the storage is a bit fragmented and confusing.

would suggest unifying these types in types.ts and have 1 interface for the Storage.

// types.ts file

import type { tasks } from "./registry"

export type TaskId = (typeof tasks)[number]["id"]

/** Metadata stored alongside data */
export interface StorageMetadata {
  storedAt: string
}

/** Result shape when retrieving data with metadata */
export interface StorageResult<T> {
  data: T
  metadata: StorageMetadata
}

/**
 * Unified storage interface.
 * Implementations must provide both get and set methods.
 */
export interface Storage {
  get<T>(taskId: TaskId): Promise<StorageResult<T> | null>
  set(taskId: TaskId, data: unknown, metadata?: StorageMetadata): Promise<void>
}

then the implementation would be cleaner

export const netlifyBlobsStorage: Storage = {
  async get<T>(taskId: TaskId) { ... },
  async set(taskId: TaskId, data: unknown, metadata?: StorageMetadata) { ... },
}

for (const task of dailyTasks) {
try {
console.log(`Fetching data for task: ${task.id}`)
const data = await task.fetchFunction()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are running these sequentially which can be a major bottleneck. We can parallelized by using await Promise.allSettled

Copy link
Copy Markdown
Member

@pettinarip pettinarip left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@corwintines overall looks fine. I've noticed the addition of unit tests, left some questions

Comment thread tests/unit/data-layer/getters.spec.ts Outdated
}

// Set USE_MOCK_DATA before importing data-layer
process.env.USE_MOCK_DATA = "true"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets avoid setting env vars in code. You are already doing "test:unit": "USE_MOCK_DATA=true playwright test --project=unit" in the scripts (which is the correct approach).

Comment thread tests/unit/data-layer/getters.spec.ts Outdated

// Register path aliases
try {
require("tsconfig-paths/register")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need to register this? is this necessary? the e2e tests did not require it

Comment thread playwright.config.ts
testDir: "./tests/e2e",
outputDir: "./tests/e2e/__results__",
testDir: "./tests",
outputDir: "./tests/__results__",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is probably need that we update GH action yaml file as well, or not?

Comment thread tests/unit/data-layer/getters.spec.ts Outdated
if (result !== null) {
// MetricReturnData is ValueOrError<number>
expect(result).toHaveProperty("value")
expect(typeof result.value).toBe("number")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get a bunch of TS errors coming mostly from accessing the value prop

tests/unit/data-layer/getters.spec.ts:23:30 - error TS2339: Property 'value' does not exist on type 'MetricReturnData'.
  Property 'value' does not exist on type '{ error: string; }'.

Copy link
Copy Markdown
Member

@pettinarip pettinarip left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@corwintines I think there’s one major blocker at the moment, related to the mocks and the amount of code being added in this PR (~54k new lines). We should probably sort that out before merging. Some of the mock files are over 500 lines, which feels a bit too big for what they need to do.

As for smaller things (that we can clean up later):

  • some unnecessary boilerplate
  • excessive comments
  • verbose logging

All of that just makes the code a bit harder to follow. I’m sure we can simplify it quite a bit.

Comment thread src/lib/data/index.ts Outdated
Comment thread src/data-layer/storage/getter.ts Outdated
Comment thread src/data-layer/mocks/fetch-apps.json
Comment thread src/data-layer/mocks/fetch-git-history.json
Comment thread src/data-layer/mocks/fetch-l2beat.json
Copy link
Copy Markdown
Member

@pettinarip pettinarip left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@corwintines I've trimmed a bit more the mocks. LGTM! gj 💪🏼

@pettinarip pettinarip merged commit 668d6b4 into dev Dec 18, 2025
7 checks passed
@pettinarip pettinarip deleted the datalayer branch December 18, 2025 20:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies 📦 Changes related to project dependencies tooling 🔧 Changes related to tooling of the project

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants