@calculator-5329/cloud-proxy
v0.3.1
Published
TypeScript client SDK for the Cloud Run proxy server
Maintainers
Readme
@calculator-5329/cloud-proxy
TypeScript client SDK for the Cloud Run proxy server. Zero runtime dependencies, fully typed, ESM-only.
Install
npm install @calculator-5329/cloud-proxyRequirements: Node 18+ (uses global fetch)
Quick Start
import { createClient } from "@calculator-5329/cloud-proxy";
const proxy = createClient({
baseUrl: process.env.PROXY_URL!,
token: process.env.PROXY_TOKEN!,
});
// Health check
const { status } = await proxy.health();Set two environment variables and you're done:
PROXY_URL=https://your-service-xyz.run.app
PROXY_TOKEN=your-static-bearer-tokenModules
Every module is accessible as a property on the client object. Modules are lazily instantiated on first access.
| Module | Accessor | Description |
|--------|----------|-------------|
| AI | proxy.ai | OpenAI, Anthropic, Gemini, OpenRouter passthrough |
| Auth | proxy.auth | Firebase Auth user management |
| Firestore | proxy.firestore | Firestore CRUD, queries, batch, aggregates |
| Storage | proxy.storage | GCS upload, download, signed URLs, buckets |
| Vectors | proxy.vectors | Vector search + Gemini embeddings |
| Setup | proxy.setup | Provisioning + project bootstrap |
| Agent | proxy.agent | Wikipedia lookup, Brave Search, event enrichment |
AI
// Direct provider passthrough
const response = await proxy.ai.openai("/v1/chat/completions", {
model: "gpt-4o",
messages: [{ role: "user", content: "Hello" }],
});
// Convenience chat — normalized response across all providers
const reply = await proxy.ai.chat({
provider: "anthropic",
model: "claude-sonnet-4-20250514",
messages: [{ role: "user", content: "Explain quantum computing" }],
maxTokens: 1024,
});
console.log(reply.content);
// Streaming
const stream = await proxy.ai.chatStream({
provider: "openai",
model: "gpt-4o",
messages: [{ role: "user", content: "Write a poem" }],
});
for await (const chunk of stream) {
process.stdout.write(new TextDecoder().decode(chunk));
}Auth
const user = await proxy.auth.createUser({
email: "[email protected]",
password: "securepassword",
displayName: "Alice",
});
await proxy.auth.setClaims(user.uid, { admin: true });
const users = await proxy.auth.listUsers({ limit: 50 });
// Email links
const { link } = await proxy.auth.generatePasswordResetLink("[email protected]");
// Session cookies
const { cookie } = await proxy.auth.createSessionCookie(idToken, 7 * 24 * 60 * 60 * 1000);
const decoded = await proxy.auth.verifySessionCookie(cookie, true);setClaims() and createSessionCookie() now send the request shape expected by the proxy service.
Firestore
// CRUD
const post = await proxy.firestore.create("posts", { title: "Hello", body: "World" });
const doc = await proxy.firestore.get<{ title: string }>("posts/abc123");
await proxy.firestore.update("posts/abc123", { title: "Updated" });
await proxy.firestore.delete("posts/abc123");
// Query
const published = await proxy.firestore.query("posts", {
filters: [{ field: "status", op: "==", value: "published" }],
orderBy: [{ field: "createdAt", direction: "desc" }],
limit: 50,
});
// Subcollections
const comments = await proxy.firestore.list("posts/abc123/comments");
// Or scoped:
const scoped = proxy.firestore.sub("posts/abc123");
await scoped.list("comments");
// Atomic field operations
import { FieldOp } from "@calculator-5329/cloud-proxy";
await proxy.firestore.patch("posts/abc123", {
views: FieldOp.increment(1),
tags: FieldOp.arrayUnion("featured"),
});
// Batch
await proxy.firestore.batch([
{ type: "create", collection: "posts", data: { title: "A" } },
{ type: "delete", collection: "posts", docId: "old" },
]);
// Aggregates
const stats = await proxy.firestore.aggregate("orders", {
aggregations: [
{ type: "count", alias: "total" },
{ type: "sum", field: "amount", alias: "revenue" },
],
filters: [{ field: "status", op: "==", value: "completed" }],
});
// Collection group queries
const allComments = await proxy.firestore.query("comments", {
collectionGroup: true,
filters: [{ field: "flagged", op: "==", value: true }],
});Storage
// Direct upload
const result = await proxy.storage.upload("my-bucket", file, {
path: "avatars",
public: true,
});
// Signed URL upload (large files)
const { uploadUrl } = await proxy.storage.getUploadUrl("my-bucket", {
filename: "video.mp4",
contentType: "video/mp4",
expiresInMinutes: 30,
});
// Download
const { downloadUrl } = await proxy.storage.getDownloadUrl("my-bucket", "photo.jpg", 120);
const response = await proxy.storage.download("my-bucket", "report.pdf");
// List & delete
const files = await proxy.storage.listFiles("my-bucket", { prefix: "avatars/" });
await proxy.storage.deleteFile("my-bucket", "old-file.txt");
// Bucket management
await proxy.storage.createBucket({ name: "new-bucket", location: "US" });
const { buckets } = await proxy.storage.listBuckets();Vectors
// Embed and store
const doc = await proxy.vectors.embed("articles", "Quantum computing uses qubits...", {
data: { title: "Intro to Quantum", category: "science" },
});
// Batch embed
await proxy.vectors.embedBatch("articles", [
{ text: "First article...", data: { title: "AI" } },
{ text: "Second article...", data: { title: "ML" } },
]);
// Semantic search
const hits = await proxy.vectors.semanticSearch("articles", "quantum physics basics", {
topK: 5,
filters: [{ field: "category", op: "==", value: "science" }],
});
// Update & delete
await proxy.vectors.update("articles", doc.id, { text: "Updated content..." });
await proxy.vectors.delete("articles", doc.id);store() now sends { data, embedding, docId? }, matching the server contract.
Setup
// Bootstrap an entire project
const status = await proxy.setup.bootstrapProject({
projectName: "my-app",
firestore: {
collections: [
{ name: "users" },
{ name: "documents", vectorIndex: true },
],
},
storage: { bucketName: "my-app-assets", public: true },
auth: { enableEmailPassword: true },
});
// Check status
const current = await proxy.setup.getStatus();Error Handling
All errors are thrown as ProxyError with code, status, and details:
import { ProxyError } from "@calculator-5329/cloud-proxy";
try {
await proxy.firestore.get("posts/nonexistent");
} catch (err) {
if (err instanceof ProxyError && err.code === "FIRESTORE_NOT_FOUND") {
// handle 404
}
}License
MIT
