Skip to content

Commit

Permalink
update spin 3 docs to represent js sdk v3 structure
Browse files Browse the repository at this point in the history
Signed-off-by: karthik2804 <[email protected]>
  • Loading branch information
karthik2804 committed Jan 28, 2025
1 parent f91fd60 commit 8d7ad4f
Show file tree
Hide file tree
Showing 11 changed files with 252 additions and 245 deletions.
45 changes: 12 additions & 33 deletions content/spin/v3/ai-sentiment-analysis-api-tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -451,16 +451,8 @@ impl FromStr for Sentiment {
{{ startTab "TypeScript"}}

```typescript
import {
HandleRequest,
HttpRequest,
HttpResponse,
Llm,
InferencingModels,
InferencingOptions,
Router,
Kv,
} from "@fermyon/spin-sdk";
import { Llm, Kv } from "@fermyon/spin-sdk";
import { AutoRouter } from 'itty-router';

interface SentimentAnalysisRequest {
sentence: string;
Expand Down Expand Up @@ -492,7 +484,7 @@ negative
<SENTENCE>
`;

async function performSentimentAnalysis(request: HttpRequest) {
async function performSentimentAnalysis(request: Request) {
// Parse sentence out of request
let data = request.json() as SentimentAnalysisRequest;
let sentence = data.sentence;
Expand All @@ -515,9 +507,9 @@ async function performSentimentAnalysis(request: HttpRequest) {

// Otherwise, perform sentiment analysis
console.log("Running inference");
let options: InferencingOptions = { maxTokens: 6 };
let options: Llm.InferencingOptions = { maxTokens: 6 };
let inferenceResult = Llm.infer(
InferencingModels.Llama2Chat,
Llm.InferencingModels.Llama2Chat,
PROMPT.replace("<SENTENCE>", sentence),
options
);
Expand All @@ -539,36 +531,23 @@ async function performSentimentAnalysis(request: HttpRequest) {
console.log("Caching sentiment in KV store");
kv.set(sentence, sentiment);

return {
status: 200,
body: JSON.stringify({
return new Response(JSON.stringify({
sentiment,
} as SentimentAnalysisResponse),
};
} as SentimentAnalysisResponse));
}

let router = Router();
let router = AutoRouter();

// Map the route to the handler
router.post("/api/sentiment-analysis", async (_, req) => {
router.post("/api/sentiment-analysis", async (req) => {
console.log(`${new Date().toISOString()} POST /sentiment-analysis`);
return await performSentimentAnalysis(req);
});

// Catch all 404 handler
router.all("/api/*", async (_, req) => {
return {
status: 404,
body: "Not found",
};
//@ts-ignore
addEventListener('fetch', async (event: FetchEvent) => {
event.respondWith(router.fetch(event.request));
});

// Entry point to the Spin handler
export const handleRequest: HandleRequest = async function (
request: HttpRequest
): Promise<HttpResponse> {
return await router.handleRequest(request, request);
};
```

{{ blockEnd }}
Expand Down
4 changes: 2 additions & 2 deletions content/spin/v3/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,12 @@ It's normally convenient to put the detailed build instructions in `package.json
```json
{
"scripts": {
"build": "npx webpack --mode=production && npx mkdirp target && npx j2w -i dist.js -d combined-wit -n combined -o target/spin-http-js.wasm"
"build": "knitwit --out-dir build/wit/knitwit --out-world combined && npx webpack --mode=production && npx mkdirp target && npx j2w -i dist.js -d combined-wit -n combined -o target/spin-http-js.wasm"
}
}
```

{{ details "Parts of the build script" "The build script calls out to [`webpack`](https://webpack.js.org/) and `j2w` which is a script provided by the `@fermyon/spin-sdk` package that utilizes [`ComponentizeJS`](https://github.com/bytecodealliance/ComponentizeJS)."}}
{{ details "Parts of the build script" "The build script calls out to [`webpack`](https://webpack.js.org/) and `j2w` which is a script provided by the `@fermyon/spin-sdk` package that utilizes [`ComponentizeJS`](https://github.com/bytecodealliance/ComponentizeJS). [`knitwit`](https://github.com/fermyon/knitwit) is a utility that helps combine `wit` worlds "}}

The build command can then call the NPM script:

Expand Down
138 changes: 60 additions & 78 deletions content/spin/v3/javascript-components.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ url = "https://github.com/fermyon/developer/blob/main/content/spin/v3/javascript
- [HTTP Components](#http-components)
- [Sending Outbound HTTP Requests](#sending-outbound-http-requests)
- [Storing Data in Redis From JS/TS Components](#storing-data-in-redis-from-jsts-components)
- [Routing in a Component](#routing-in-a-component)
- [Storing Data in the Spin Key-Value Store](#storing-data-in-the-spin-key-value-store)
- [Storing Data in SQLite](#storing-data-in-sqlite)
- [Storing Data in MySQL and PostgreSQL Relational Databases](#storing-data-in-mysql-and-postgresql-relational-databases)
Expand Down Expand Up @@ -76,13 +75,13 @@ This creates a directory of the following structure:
<!-- @nocpy -->

```text
hello-world/
├── knitwit.json
hello-world
├── config
│ └── knitwit.json
├── package.json
├── spin.toml
├── src
│   ├── index.ts
│   └── spin.ts
│ └── index.ts
├── tsconfig.json
└── webpack.config.js
```
Expand Down Expand Up @@ -134,47 +133,53 @@ for writing Spin components with the Spin JS/TS SDK.
> Make sure to read [the page describing the HTTP trigger](./http-trigger.md) for more
> details about building HTTP applications.
Building a Spin HTTP component using the JS/TS SDK means writing a single function
that takes an HTTP request and a Response Builder which can be used to return an HTTP response as a parameter.
Building a Spin HTTP component with the JavaScript/TypeScript SDK now involves adding an event listener for the `fetch` event. This event listener handles incoming HTTP requests and allows you to construct and return HTTP responses.

Below is a complete implementation for such a component in TypeScript:

```javascript
import { ResponseBuilder } from "@fermyon/spin-sdk";
import { AutoRouter } from 'itty-router';

export async function handler(req: Request, res: ResponseBuilder) {
console.log(req);
res.send("hello universe");
}
```
let router = AutoRouter();

router
.get("/", () => new Response("hello universe"))
.get('/hello/:name', ({ name }) => `Hello, ${name}!`)

The important things to note in the implementation above:
//@ts-ignore
addEventListener('fetch', async (event: FetchEvent) => {
event.respondWith(router.fetch(event.request));
});

- The `handler` function is the entry point for the Spin component.
- The execution of the function terminates once `res.send` or `res.end` is called.
```

## Sending Outbound HTTP Requests

If allowed, Spin components can send outbound HTTP requests.
Let's see an example of a component that makes a request to [an API that returns random animal facts](https://random-data-api.fermyon.app/animals/json)

```javascript
import { ResponseBuilder } from "@fermyon/spin-sdk";

interface AnimalFact {
timestamp: number;
fact: string;
}
import { AutoRouter } from 'itty-router';

export async function handler(req: Request, res: ResponseBuilder) {
const animalFactResponse = await fetch("https://random-data-api.fermyon.app/animals/json")
const animalFact = await animalFactResponse.json() as AnimalFact
let router = AutoRouter();

const body = `Here's an animal fact: ${animalFact.fact}\n`
router
.get("*", getDataFromAPI)

res.set({"content-type": "text/plain"})
res.send(body)
async function getDataFromAPI(_request: Request) {
let response = await fetch(
'https://random-data-api.fermyon.app/physics/json',
);
let data = await response.json();
let fact = `Here is a fact: ${data.fact}`;
return new Response(fact);
}

//@ts-ignore
addEventListener('fetch', async (event: FetchEvent) => {
event.respondWith(router.fetch(event.request));
});

```

Before we can execute this component, we need to add the `random-data-api.fermyon.app`
Expand Down Expand Up @@ -216,7 +221,7 @@ content-type: application/json; charset=utf-8
content-length: 185
server: spin/0.1.0
Here's an animal fact: Reindeer grow new antlers every year
Here is a fact: Reindeer grow new antlers every year
```

> Without the `allowed_outbound_hosts` field populated properly in `spin.toml`,
Expand Down Expand Up @@ -245,31 +250,35 @@ Using the Spin's JS SDK, you can use the Redis key/value store and to publish me
Let's see how we can use the JS/TS SDK to connect to Redis:

```javascript
import { ResponseBuilder, Redis } from '@fermyon/spin-sdk';
import { AutoRouter } from 'itty-router';
import { Redis } from '@fermyon/spin-sdk';

const encoder = new TextEncoder();
const decoder = new TextDecoder();
const redisAddress = 'redis://localhost:6379/';

export async function handler(_req: Request, res: ResponseBuilder) {
try {
let db = Redis.open(redisAddress);
db.set('test', encoder.encode('Hello world'));
let val = db.get('test');

if (!val) {
res.status(404);
res.send();
return;
}
// publish to a channel names "message"
db.publish("message", val)
res.send(val);
} catch (e: any) {
res.status(500);
res.send(`Error: ${JSON.stringify(e.payload)}`);
}
}
let router = AutoRouter();

router
.get("/", () => {
try {
let db = Redis.open(redisAddress);
db.set('test', encoder.encode('Hello world'));
let val = db.get('test');

if (!val) {
return new Response(null, { status: 404 });
}
return new Response(val);
} catch (e: any) {
return new Response(`Error: ${JSON.stringify(e.payload)}`, { status: 500 });
}
})

//@ts-ignore
addEventListener('fetch', async (event: FetchEvent) => {
event.respondWith(router.fetch(event.request));
});

```

This HTTP component demonstrates fetching a value from Redis by key, setting a key with a value, and publishing a message to a Redis channel.
Expand All @@ -285,33 +294,6 @@ As with all networking APIs, you must grant access to Redis hosts via the `allow
allowed_outbound_hosts = ["redis://localhost:6379"]
```

## Routing in a Component

The JavaScript/TypeScript SDK provides a router that makes it easier to handle routing within a component. The router is based on [`itty-router`](https://www.npmjs.com/package/itty-router). An additional function `handleRequest` has been implemented in the router to allow passing in the Spin HTTP request directly. An example usage of the router is given below:

```javascript
import { ResponseBuilder, Router } from '@fermyon/spin-sdk';

let router = Router();

router.get("/", (_, req, res) => { handleDefaultRoute(req, res) })
router.get("/home/:id", (metadata, req, res) => { handleHomeRoute(req, res, metadata.params.id) })

async function handleDefaultRoute(_req: Request, res: ResponseBuilder) {
res.set({ "content-type": "text/plain" });
res.send("Hello from default route");
}

async function handleHomeRoute(_req: Request, res: ResponseBuilder, id: string) {
res.set({ "content-type": "text/plain" });
res.send(`Hello from home route with id: ${id}`);
}

export async function handler(req: Request, res: ResponseBuilder) {
await router.handleRequest(req, res);
}
```

## Storing Data in the Spin Key-Value Store

Spin has a key-value store built in. For information about using it from TypeScript/JavaScript, see [the key-value store tutorial](key-value-store-tutorial).
Expand Down Expand Up @@ -381,5 +363,5 @@ These are some of the suggested libraries that have been tested and confirmed to

## Caveats

- All `spin-sdk` related functions and methods (like `Variables`, `Redis`, `Mysql`, `Pg`, `Kv` and `Sqlite`) can be called only inside the `handler` function. This includes `fetch`. Any attempts to use it outside the function will lead to an error. This is due to Wizer using only Wasmtime to execute the script at build time, which does not include any Spin SDK support.
- All `spin-sdk` related functions and methods (like `Variables`, `Redis`, `Mysql`, `Pg`, `Kv` and `Sqlite`) can be called only inside the fetch event handler. This includes `fetch`. Any attempts to use it outside the function will lead to an error. This is due to Wizer using only Wasmtime to execute the script at build time, which does not include any Spin SDK support.
- No crypto operation that involve handling private keys are supported.
Loading

0 comments on commit 8d7ad4f

Please sign in to comment.