Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"private": true,
"type": "module",
"packageManager": "pnpm@10.18.2+sha512.9fb969fa749b3ade6035e0f109f0b8a60b5d08a1a87fdf72e337da90dcc93336e2280ca4e44f2358a649b83c17959e9993e777c2080879f3801e6f0d999ad3dd",
"packageManager": "pnpm@10.30.0+sha512.2b5753de015d480eeb88f5b5b61e0051f05b4301808a82ec8b840c9d2adf7748eb352c83f5c1593ca703ff1017295bc3fdd3119abb9686efc96b9fcb18200937",
"scripts": {
"build": "tsc -b tsconfig.build.json && pnpm --recursive --parallel build",
"check": "tsc -b tsconfig.json",
Expand All @@ -12,16 +12,16 @@
"devDependencies": {
"@edgeandnode/amp": "workspace:*",
"@effect/eslint-plugin": "^0.3.2",
"@effect/language-service": "^0.56.0",
"@effect/language-service": "^0.74.0",
"@eslint/js": "^9.39.2",
"eslint": "^9.39.2",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import-x": "^4.16.1",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-sort-destructure-keys": "^2.0.0",
"eslint-plugin-sort-destructure-keys": "^3.0.0",
"prettier": "^3.8.1",
"typescript": "^5.9.3",
"typescript-eslint": "^8.54.0",
"vitest": "4.0.12"
"vitest": "4.0.18"
}
}
4,927 changes: 2,475 additions & 2,452 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

37 changes: 19 additions & 18 deletions typescript/amp/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@edgeandnode/amp",
"version": "0.0.51",
"version": "0.0.52",
"type": "module",
"license": "BUSL-1.1",
"description": "Build and manage blockchain datasets.",
Expand Down Expand Up @@ -49,37 +49,38 @@
}
},
"dependencies": {
"@effect/cli": "^0.72.1",
"abitype": "^1.1.2",
"@effect/cli": "^0.73.2",
"abitype": "^1.2.3",
"jiti": "^2.6.1",
"jose": "^6.1.2",
"jose": "^6.1.3",
"js-toml": "^1.0.2",
"minimatch": "^10.1.2",
"minimatch": "^10.2.1",
"open": "^11.0.0",
"viem": "^2.39.3"
"viem": "^2.46.1"
},
"peerDependencies": {
"@bufbuild/protobuf": "^2.10.1",
"@bufbuild/protobuf": "^2.11.0",
"@connectrpc/connect": "^2.1.1",
"@connectrpc/connect-node": "^2.1.1",
"@effect/platform": "^0.93.3",
"@effect/platform-node": "^0.101.0",
"@effect/platform": "^0.94.5",
"@effect/platform-node": "^0.104.1",
"apache-arrow": "^21.1.0",
"effect": "^3.19.5"
"effect": "^3.19.18"
},
"devDependencies": {
"@bufbuild/buf": "^1.60.0",
"@bufbuild/protoc-gen-es": "^2.10.1",
"@bufbuild/buf": "^1.65.0",
"@bufbuild/protoc-gen-es": "^2.11.0",
"@connectrpc/connect": "^2.1.1",
"@connectrpc/connect-node": "^2.1.1",
"@effect/platform": "^0.93.3",
"@effect/platform-node": "^0.101.0",
"@effect/cluster": "^0.56.4",
"@effect/platform": "^0.94.5",
"@effect/platform-node": "^0.104.1",
"@effect/vitest": "^0.27.0",
"@types/node": "^24.10.1",
"@types/node": "^25.2.3",
"apache-arrow": "^21.1.0",
"effect": "^3.19.5",
"tsx": "^4.20.6",
"effect": "^3.19.18",
"tsx": "^4.21.0",
"typescript": "^5.9.3",
"vitest": "4.0.12"
"vitest": "4.0.18"
}
}
25 changes: 10 additions & 15 deletions typescript/amp/scripts/copy-studio-dist.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import { FileSystem, Path } from "@effect/platform"
import { NodeContext } from "@effect/platform-node"
import { Effect } from "effect"
import { cpSync, mkdirSync } from "node:fs"
import { resolve } from "node:path"

const program = Effect.gen(function*() {
const fs = yield* FileSystem.FileSystem
const path = yield* Path.Path
function copyStudioDist() {
const src = resolve("../", "studio", "dist")
const dest = resolve("./", "dist", "studio", "dist")

const src = path.resolve("../", "studio", "dist")
const dest = path.resolve("./", "dist", "studio", "dist")
mkdirSync(dest, { recursive: true })
cpSync(src, dest, { recursive: true, force: true })

yield* fs
.makeDirectory(dest, { recursive: true })
.pipe(Effect.andThen(() => fs.copy(src, dest, { overwrite: true })))
console.info("[Build] Copied studio/dist to dist/studio/dist")
}

return yield* Effect.logInfo("[Build] Copied studio/dist to dist/studio/dist")
}).pipe(Effect.provide(NodeContext.layer))

Effect.runPromise(program).catch(console.error)
copyStudioDist()
59 changes: 30 additions & 29 deletions typescript/amp/src/AmpRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@ export class AmpRegistryService extends Effect.Service<AmpRegistryService>()("Am
const getRequest = <A, I>(
url: string,
responseSchema: Schema.Schema<A, I, never>,
auth?: Auth.AuthStorageSchema,
accessToken?: Model.AccessToken,
): Effect.Effect<Option.Option<A>, RegistryApiError, never> => {
const request = HttpClientRequest.get(url, { acceptJson: true })
const authenticatedRequest = auth
? request.pipe(HttpClientRequest.bearerToken(auth.accessToken))
const authenticatedRequest = accessToken != null
? request.pipe(HttpClientRequest.bearerToken(accessToken))
: request

return authenticatedRequest.pipe(
Expand All @@ -89,7 +89,7 @@ export class AmpRegistryService extends Effect.Service<AmpRegistryService>()("Am
Effect.flatMap(
HttpClientResponse.matchStatus({
404: () => Effect.succeed(Option.none<A>()),
...(auth ? { 401: (response) => parseRegistryError(response) } : {}),
...(accessToken != null ? { 401: (response) => parseRegistryError(response) } : {}),
"2xx": (response) =>
HttpClientResponse.schemaBodyJson(responseSchema)(response).pipe(
Effect.map(Option.some),
Expand All @@ -111,7 +111,7 @@ export class AmpRegistryService extends Effect.Service<AmpRegistryService>()("Am
const makeAuthenticatedRequest = <A, AE, I, IE>(
method: "POST" | "PUT",
url: string,
auth: Auth.AuthStorageSchema,
accessToken: Model.AccessToken,
bodySchema: Schema.Schema<I, IE, never>,
body: I,
responseSchema: Schema.Schema<A, AE, never>,
Expand All @@ -138,7 +138,7 @@ export class AmpRegistryService extends Effect.Service<AmpRegistryService>()("Am
})

return yield* request.pipe(
HttpClientRequest.bearerToken(auth.accessToken),
HttpClientRequest.bearerToken(accessToken),
HttpClientRequest.setBody(encodedBody),
client.execute,
handleHttpClientErrors,
Expand Down Expand Up @@ -174,99 +174,99 @@ export class AmpRegistryService extends Effect.Service<AmpRegistryService>()("Am

/**
* Get a dataset owned by the authenticated user (including private datasets)
* @param auth - Authenticated user's auth storage
* @param accessToken - Authenticated user's access token
* @param namespace - Dataset namespace
* @param name - Dataset name
* @returns Option.some(dataset) if found, Option.none() if not found
*/
const getOwnedDataset = (
auth: Auth.AuthStorageSchema,
accessToken: Model.AccessToken,
namespace: Model.DatasetNamespace,
name: Model.DatasetName,
): Effect.Effect<Option.Option<AmpRegistryDatasetDto>, RegistryApiError, never> =>
getRequest(buildUrl(`api/v1/owners/@me/datasets/${namespace}/${name}`), AmpRegistryDatasetDto, auth)
getRequest(buildUrl(`api/v1/owners/@me/datasets/${namespace}/${name}`), AmpRegistryDatasetDto, accessToken)

/**
* Get dataset by trying public endpoint first, then owned endpoint as fallback
* @param auth - Auth for owned dataset lookup
* @param accessToken - Authenticated user's access token
* @param namespace - Dataset namespace
* @param name - Dataset name
* @returns Option of dataset from either endpoint
*/
const getDatasetWithFallback = (
auth: Auth.AuthStorageSchema,
accessToken: Model.AccessToken,
namespace: Model.DatasetNamespace,
name: Model.DatasetName,
): Effect.Effect<Option.Option<AmpRegistryDatasetDto>, RegistryApiError, never> =>
getDataset(namespace, name).pipe(
Effect.flatMap((publicResult) =>
Option.match(publicResult, {
onSome: (dataset) => Effect.succeed(Option.some(dataset)),
onNone: () => getOwnedDataset(auth, namespace, name),
onNone: () => getOwnedDataset(accessToken, namespace, name),
})
),
)

/**
* Publish a new dataset
* @param auth - Authenticated user's auth storage
* @param accessToken - Authenticated user's access token
* @param dto - Dataset data to publish
* @returns Created dataset
*/
const publishDataset = (
auth: Auth.AuthStorageSchema,
accessToken: Model.AccessToken,
dto: AmpRegistryInsertDatasetDto,
): Effect.Effect<AmpRegistryDatasetDto, RegistryApiError, never> =>
makeAuthenticatedRequest(
"POST",
buildUrl("api/v1/owners/@me/datasets/publish"),
auth,
accessToken,
AmpRegistryInsertDatasetDto,
dto,
AmpRegistryDatasetDto,
)

/**
* Publish a new version to an existing dataset
* @param auth - Authenticated user's auth storage
* @param accessToken - Authenticated user's access token
* @param namespace - Dataset namespace
* @param name - Dataset name
* @param dto - Version data to publish
* @returns Created version
*/
const publishVersion = (
auth: Auth.AuthStorageSchema,
accessToken: Model.AccessToken,
namespace: Model.DatasetNamespace,
name: Model.DatasetName,
dto: AmpRegistryInsertDatasetVersionDto,
): Effect.Effect<AmpRegistryDatasetVersionDto, RegistryApiError, never> =>
makeAuthenticatedRequest(
"POST",
buildUrl(`api/v1/owners/@me/datasets/${namespace}/${name}/versions/publish`),
auth,
accessToken,
AmpRegistryInsertDatasetVersionDto,
dto,
AmpRegistryDatasetVersionDto,
)

/**
* Update mutable metadata fields on an existing dataset
* @param auth - Authenticated user's auth storage
* @param accessToken - Authenticated user's access token
* @param namespace - Dataset namespace
* @param name - Dataset name
* @param dto - Metadata update data
* @returns Updated dataset
*/
const updateDatasetMetadata = (
auth: Auth.AuthStorageSchema,
accessToken: Model.AccessToken,
namespace: Model.DatasetNamespace,
name: Model.DatasetName,
dto: AmpRegistryUpdateDatasetMetadataDto,
): Effect.Effect<AmpRegistryDatasetDto, RegistryApiError, never> =>
makeAuthenticatedRequest(
"PUT",
buildUrl(`api/v1/owners/@me/datasets/${namespace}/${name}`),
auth,
accessToken,
AmpRegistryUpdateDatasetMetadataDto,
dto,
AmpRegistryDatasetDto,
Expand Down Expand Up @@ -340,7 +340,7 @@ export class AmpRegistryService extends Effect.Service<AmpRegistryService>()("Am
*/
const publishFlow = Effect.fn("DatasetPublishFlow")(function*(
args: Readonly<{
auth: Auth.AuthStorageSchema
auth: Auth.AuthStorageSchema | Auth.JWKSVerifiedAuthClaims
context: ManifestContext.DatasetContext
versionTag: Model.DatasetRevision
changelog?: string | undefined
Expand All @@ -357,23 +357,24 @@ export class AmpRegistryService extends Effect.Service<AmpRegistryService>()("Am
const indexingChains = [...new Set(Object.values(manifest.tables).map((table) => table.network))]

// Check if dataset exists (try public first, then owned/private)
const maybeDataset = yield* getDatasetWithFallback(auth, namespace, name)
const maybeDataset = yield* getDatasetWithFallback(auth.accessToken, namespace, name)

const owner = auth instanceof Auth.AuthStorageSchema ? (auth.accounts ?? []) : [auth.subject]

return yield* Option.match(maybeDataset, {
// Dataset exists - validate ownership and publish new version
onSome: (dataset) =>
Effect.gen(function*() {
// Validate ownership
const accounts = auth.accounts ?? []
const isOwner = accounts.some((account) => account.toLowerCase() === dataset.owner.toLowerCase())
const isOwner = owner.some((account) => account.toLowerCase() === dataset.owner.toLowerCase())

if (!isOwner) {
return yield* Effect.fail(
new DatasetOwnershipError({
namespace,
name,
actualOwner: dataset.owner,
userAddresses: accounts,
userAddresses: owner,
}),
)
}
Expand All @@ -393,7 +394,7 @@ export class AmpRegistryService extends Effect.Service<AmpRegistryService>()("Am

// Publish new version
yield* publishVersion(
auth,
auth.accessToken,
namespace,
name,
AmpRegistryInsertDatasetVersionDto.make({
Expand All @@ -410,7 +411,7 @@ export class AmpRegistryService extends Effect.Service<AmpRegistryService>()("Am
const metadataChanged = hasMetadataChanged(dataset, metadata, indexingChains)
if (metadataChanged) {
yield* updateDatasetMetadata(
auth,
auth.accessToken,
namespace,
name,
AmpRegistryUpdateDatasetMetadataDto.make({
Expand All @@ -431,7 +432,7 @@ export class AmpRegistryService extends Effect.Service<AmpRegistryService>()("Am
// Dataset doesn't exist - publish new dataset with initial version
onNone: () =>
publishDataset(
auth,
auth.accessToken,
AmpRegistryInsertDatasetDto.make({
namespace,
name,
Expand Down
Loading
Loading