Skip to content

Conversation

@emily-shen
Copy link
Contributor

@emily-shen emily-shen commented Jan 23, 2026

By commit:

  • rename getLocalExplorerFromEnv() - stray fixup from last PR
  • Add hono and chanfana dependencies for Local Explorer API - https://chanfana.pages.dev/ lets you do typescript -> openapi schema instead of the other way around, and i really really hate writing openapi specs directly.
  • Add binding ID map - adds binding name to id map as a json binding so we can access the id at runtime (needed for routing)
  • actually implement the endpoints. sorry this is a big one and i am very happy claude existed for this commit. I would really appreciate some more cross referencing between the API docs and this. I have created a ticket DEVX-2425 to diff the prod openapi spec and our local one so it will be easier in the future.

Also notes on what i have implemented in the API:

  • GET /storage/kv/namespaces - List namespaces
  • GET /storage/kv/namespaces/:id/keys - List keys
  • GET /storage/kv/namespaces/:id/values/:key - Get value
  • PUT /storage/kv/namespaces/:id/values/:key - Write value
  • DELETE /storage/kv/namespaces/:id/values/:key - Delete key
  • POST /storage/kv/namespaces/:id/bulk/get - Bulk get values

bulk is not actually used in the dash, the dash seems to individually get each value, but that seems silly to do locally so i added bulk get as well.

I have not implemented key metadata (expiration_ttl etc), supports_url_encoding (we assume it is true, might be a legacy thing?), and prefix filtering when listing keys. I also haven't checked if the error codes are correct, DEVX-2426 will followup on errors.


  • Tests
    • Tests included/updated
    • Automated tests not possible - manual testing has been completed as follows:
    • Additional testing not necessary because:
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: wip experimental thingy

A picture of a cute animal (not mandatory, but encouraged)

image

Pass a JSON binding map to the Local Explorer worker that maps binding
names to actual namespace IDs, allowing correct ID resolution when
binding name differs from namespace ID.
@changeset-bot
Copy link

changeset-bot bot commented Jan 23, 2026

🦋 Changeset detected

Latest commit: 1dc92f9

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 23, 2026

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@12075

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@12075

miniflare

npm i https://pkg.pr.new/miniflare@12075

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@12075

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@12075

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@12075

@cloudflare/vitest-pool-workers

npm i https://pkg.pr.new/@cloudflare/vitest-pool-workers@12075

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@12075

@cloudflare/workers-utils

npm i https://pkg.pr.new/@cloudflare/workers-utils@12075

wrangler

npm i https://pkg.pr.new/wrangler@12075

commit: 1dc92f9

@emily-shen emily-shen force-pushed the emily/api branch 3 times, most recently from 39c3572 to ad0bcea Compare January 23, 2026 18:36
Add REST API endpoints for KV storage operations:
- GET /storage/kv/namespaces - List namespaces
- GET /storage/kv/namespaces/:id/keys - List keys
- GET /storage/kv/namespaces/:id/values/:key - Get value
- PUT /storage/kv/namespaces/:id/values/:key - Write value
- DELETE /storage/kv/namespaces/:id/values/:key - Delete key
- POST /storage/kv/namespaces/:id/bulk/get - Bulk get values
const response = await fetch(
`http://${ip}:${port}/cdn-cgi/explorer/api/storage/kv/namespaces`
);
const text = await response.text();
Copy link
Contributor

Choose a reason for hiding this comment

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

Should it be JSON (and should we check the content type ?)

Comment on lines +19 to +28
const KVNamespaceSchema = z.object({
id: z.string(),
title: z.string(),
});

const KVKeySchema = z.object({
name: z.string(),
expiration: z.number().optional(),
metadata: z.unknown().optional(),
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Do ns id, title, key name have max length?

const bindingName = bindingMap[namespaceId];
if (!bindingName) return null;

return env[bindingName] as KVNamespace;
Copy link
Contributor

Choose a reason for hiding this comment

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

Validate against the schema before casting?

direction: z
.enum(["asc", "desc"])
.default("asc")
.describe("Direction to order namespaces"),
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
.describe("Direction to order namespaces"),
.describe("Namespaces sort order"),

// KV Helpers
// ============================================================================

// Get a KV binding by namespace ID
Copy link
Contributor

Choose a reason for hiding this comment

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

Proper JSDoc would not harm

},
};

async handle(c: AppContext) {
Copy link
Contributor

Choose a reason for hiding this comment

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

JSDoc?

Comment on lines +105 to +108
const aVal = a[order];
const bVal = b[order];
const cmp = aVal.localeCompare(bVal);
return direction === "asc" ? cmp : -cmp;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const aVal = a[order];
const bVal = b[order];
const cmp = aVal.localeCompare(bVal);
return direction === "asc" ? cmp : -cmp;
return (direction === "asc" ? 1 : -1) * a[order].localeCompare(b[order])

}));

namespaces.sort(
(a: { id: string; title: string }, b: { id: string; title: string }) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could the type be retrieved from the schema instead of duplicated here?

Comment on lines +116 to +117
const endIndex = startIndex + per_page;
namespaces = namespaces.slice(startIndex, endIndex);
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: inline if not used elsewhere?

Suggested change
const endIndex = startIndex + per_page;
namespaces = namespaces.slice(startIndex, endIndex);
namespaces = namespaces.slice(startIndex, startIndex + per_page);

}

const listResult = await kv.list({ cursor, limit });
const resultCursor = "cursor" in listResult ? listResult.cursor ?? "" : "";
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: inline in the obj if onyl used once?

// Response Helpers
// ============================================================================

export function wrapResponse<T>(result: T) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would love to see more JSDoc across this PR.
We don't know what response this takes nor what it returns

title: "Local Explorer API",
version: "1.0.0",
description:
"A local subset of Cloudflare's REST API for exploring resources during local development.",
Copy link
Contributor

Choose a reason for hiding this comment

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

A local subset of Cloudflare's REST API

Have we changed our mind on this ?

"get-port": "^7.1.0",
"glob-to-regexp": "0.4.1",
"heap-js": "^2.5.0",
"hono": "^4.11.5",
Copy link
Contributor

Choose a reason for hiding this comment

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

Curious about why we use hono for vs its size?
Looks like the route matching we do is pretty basic and would not really require an external lib or maybe a tiny one?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Untriaged

Development

Successfully merging this pull request may close these issues.

2 participants