Skip to content
Draft
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
3 changes: 3 additions & 0 deletions examples/cloudflare-ai-search-namespace/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

# sst
.sst
87 changes: 87 additions & 0 deletions examples/cloudflare-ai-search-namespace/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Resource } from "sst";

export default {
async fetch(req: Request) {
const url = new URL(req.url);
const path = url.pathname;

// List all instances in the namespace.
if (req.method === "GET" && path === "/instances") {
const result = await Resource.Search.list();
return Response.json(result);
}

// Create an instance: POST /instances { "id": "my-docs" }
if (req.method === "POST" && path === "/instances") {
const body = (await req.json()) as { id: string };
if (!body.id)
return Response.json({ error: "Missing 'id'" }, { status: 400 });
const instance = await Resource.Search.create({ id: body.id });
const info = await instance.info();
return Response.json(info, { status: 201 });
}

// Delete an instance: DELETE /instances/my-docs
const deleteMatch = path.match(/^\/instances\/([^/]+)$/);
if (req.method === "DELETE" && deleteMatch) {
await Resource.Search.delete(deleteMatch[1]);
return new Response(null, { status: 204 });
}

// Upload a document to a specific instance:
// PUT /instances/my-docs/items?filename=guide.md
const uploadMatch = path.match(/^\/instances\/([^/]+)\/items$/);
if (req.method === "PUT" && uploadMatch) {
const instance = Resource.Search.get(uploadMatch[1]);
const filename = url.searchParams.get("filename");
if (!filename || !req.body)
return Response.json(
{ error: "Provide ?filename= and a request body" },
{ status: 400 },
);
const item = await instance.items.uploadAndPoll(filename, req.body);
return Response.json(item, { status: 201 });
}

// Search a specific instance:
// GET /instances/my-docs/search?q=caching
const searchMatch = path.match(/^\/instances\/([^/]+)\/search$/);
if (req.method === "GET" && searchMatch) {
const instance = Resource.Search.get(searchMatch[1]);
const query = url.searchParams.get("q") ?? "";
const results = await instance.search({ query });
return Response.json(results);
}

// Search across multiple instances at once:
// GET /search?q=caching&instances=docs,blog
if (req.method === "GET" && path === "/search") {
const query = url.searchParams.get("q") ?? "";
const ids = (url.searchParams.get("instances") ?? "").split(",");
if (!ids.length)
return Response.json(
{ error: "Provide ?instances=id1,id2" },
{ status: 400 },
);
const results = await Resource.Search.search({
query,
ai_search_options: { instance_ids: ids },
});
return Response.json(results);
}

return Response.json(
{
routes: [
"GET /instances — list instances",
"POST /instances {id} — create instance",
"DELETE /instances/:name — delete instance",
"PUT /instances/:name/items?filename — upload document",
"GET /instances/:name/search?q= — search one instance",
"GET /search?q=&instances=a,b — search across instances",
],
},
{ status: 404 },
);
},
};
10 changes: 10 additions & 0 deletions examples/cloudflare-ai-search-namespace/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "cloudflare-ai-search-namespace",
"version": "0.0.0",
"dependencies": {
"sst": "file:../../sdk/js"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20260424.1"
}
}
38 changes: 38 additions & 0 deletions examples/cloudflare-ai-search-namespace/sst.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/// <reference path="./.sst/platform/config.d.ts" />

/**
* ## Cloudflare AI Search Namespace
*
* Bind to an AI Search namespace to manage multiple instances at runtime. You
* can create, list, search, and delete instances dynamically — useful for
* multi-tenant apps or admin tools.
*
* Every Cloudflare account has a `default` namespace. You can also create
* custom namespaces to isolate groups of instances.
*
* For a simpler single-instance setup, see the `cloudflare-ai-search` example.
*/
export default $config({
app(input) {
return {
name: "cloudflare-ai-search-namespace",
removal: input?.stage === "production" ? "retain" : "remove",
home: "cloudflare",
};
},
async run() {
const search = new sst.cloudflare.AiSearch("Search", {
namespace: "default",
});

const worker = new sst.cloudflare.Worker("Worker", {
handler: "index.ts",
link: [search],
url: true,
});

return {
url: worker.url,
};
},
});
1 change: 1 addition & 0 deletions examples/cloudflare-ai-search-namespace/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
3 changes: 3 additions & 0 deletions examples/cloudflare-ai-search/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

# sst
.sst
81 changes: 81 additions & 0 deletions examples/cloudflare-ai-search/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Resource } from "sst";

export default {
async fetch(req: Request) {
const url = new URL(req.url);

// Upload a document: PUT /items?filename=guide.md
if (req.method === "PUT" && url.pathname === "/items") {
const filename = url.searchParams.get("filename");
if (!filename || !req.body)
return Response.json(
{ error: "Provide ?filename= and a request body" },
{ status: 400 },
);
const item = await Resource.Search.items.uploadAndPoll(
filename,
req.body,
);
return Response.json(item, { status: 201 });
}

// Search: GET /search?q=how+does+caching+work
if (req.method === "GET" && url.pathname === "/search") {
const query = url.searchParams.get("q") ?? "";
const results = await Resource.Search.search({ query });
return Response.json(results);
}

// Search with filters: GET /search?q=deploy&filter_field=category&filter_value=docs
// Demonstrates metadata filtering with Vectorize filter syntax.
if (req.method === "GET" && url.pathname === "/search/filtered") {
const query = url.searchParams.get("q") ?? "";
const field = url.searchParams.get("filter_field") ?? "";
const value = url.searchParams.get("filter_value") ?? "";
const results = await Resource.Search.search({
query,
ai_search_options: {
retrieval: {
filters: { [field]: value },
},
},
});
return Response.json(results);
}

// Chat completions: POST /chat { "question": "What is Cloudflare?" }
// Returns an AI-generated answer grounded in your indexed content.
if (req.method === "POST" && url.pathname === "/chat") {
const body = (await req.json()) as { question: string };
const answer = await Resource.Search.chatCompletions({
messages: [{ role: "user", content: body.question }],
});
return Response.json(answer);
}

// Streaming chat: POST /chat/stream { "question": "What is Cloudflare?" }
if (req.method === "POST" && url.pathname === "/chat/stream") {
const body = (await req.json()) as { question: string };
const stream = await Resource.Search.chatCompletions({
messages: [{ role: "user", content: body.question }],
stream: true,
});
return new Response(stream, {
headers: { "content-type": "text/event-stream" },
});
}

return Response.json(
{
routes: [
"PUT /items?filename= — upload a document (body = content)",
"GET /search?q= — search indexed content",
"GET /search/filtered?q=&... — search with metadata filters",
"POST /chat {question} — AI answer grounded in your content",
"POST /chat/stream {question} — streaming AI answer",
],
},
{ status: 404 },
);
},
};
10 changes: 10 additions & 0 deletions examples/cloudflare-ai-search/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "cloudflare-ai-search",
"version": "0.0.0",
"dependencies": {
"sst": "file:../../sdk/js"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20260424.1"
}
}
36 changes: 36 additions & 0 deletions examples/cloudflare-ai-search/sst.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/// <reference path="./.sst/platform/config.d.ts" />

/**
* ## Cloudflare AI Search
*
* Bind to a single AI Search instance and link it to a worker. The instance
* must already exist in your Cloudflare account — you can create one in the
* dashboard or with the namespace binding example.
*
* Once linked, you can search your indexed content and get AI-generated
* answers using chat completions.
*/
export default $config({
app(input) {
return {
name: "cloudflare-ai-search",
removal: input?.stage === "production" ? "retain" : "remove",
home: "cloudflare",
};
},
async run() {
const search = new sst.cloudflare.AiSearch("Search", {
instance: "my-docs",
});

const worker = new sst.cloudflare.Worker("Worker", {
handler: "index.ts",
link: [search],
url: true,
});

return {
url: worker.url,
};
},
});
1 change: 1 addition & 0 deletions examples/cloudflare-ai-search/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
20 changes: 11 additions & 9 deletions pkg/types/typescript/typescript.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,17 @@ type WarningEvent struct {
}

var mapping = map[string]string{
"aiBindings": "Ai",
"r2BucketBindings": "R2Bucket",
"d1DatabaseBindings": "D1Database",
"kvNamespaceBindings": "KVNamespace",
"queueBindings": "Queue",
"serviceBindings": "Service",
"hyperdriveBindings": "Hyperdrive",
"versionMetadataBindings": "WorkerVersionMetadata",
"workflowBindings": "Workflow",
"aiBindings": "Ai",
"r2BucketBindings": "R2Bucket",
"d1DatabaseBindings": "D1Database",
"kvNamespaceBindings": "KVNamespace",
"queueBindings": "Queue",
"serviceBindings": "Service",
"hyperdriveBindings": "Hyperdrive",
"versionMetadataBindings": "WorkerVersionMetadata",
"aiSearchBindings": "AiSearchInstance",
"aiSearchNamespaceBindings": "AiSearchNamespace",
"workflowBindings": "Workflow",
}

var header = strings.Join([]string{
Expand Down
Loading