diff --git a/examples/csv-importer/package.json b/examples/csv-importer/package.json index abda280..c98e90a 100644 --- a/examples/csv-importer/package.json +++ b/examples/csv-importer/package.json @@ -7,7 +7,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "framer-api": "^0.0.1-alpha.6", + "framer-api": "^0.0.1-beta.0", "papaparse": "^5.5.3", "typescript": "^5.9.3" }, diff --git a/examples/json-api/README.md b/examples/json-api/README.md new file mode 100644 index 0000000..9c67d20 --- /dev/null +++ b/examples/json-api/README.md @@ -0,0 +1,38 @@ +# JSON API + +A simple HTTP/JSON server that exposes Framer collections via REST endpoints using [Hono](https://hono.dev). + +## Usage + +```bash +EXAMPLE_PROJECT_URL="https://framer.com/projects/..." npm run dev +``` + +The server runs on port 3000 by default. + +## Endpoints + +### List Collections + +``` +GET /collections +``` + +Returns all collections in the project. + +### List Items + +``` +GET /collections/:collectionId +``` + +Returns all items in a collection by ID. + +### Get Item + +``` +GET /collections/:collectionId/:itemId +``` + +Returns a single item with its field data. + diff --git a/examples/json-api/package.json b/examples/json-api/package.json new file mode 100644 index 0000000..0663411 --- /dev/null +++ b/examples/json-api/package.json @@ -0,0 +1,19 @@ +{ + "name": "json-api", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "dev": "node --watch src/index.ts", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@hono/node-server": "^1.14.1", + "framer-api": "^0.0.1-beta.0", + "hono": "^4.7.10", + "typescript": "^5.9.3" + }, + "devDependencies": { + "@types/node": "^22.10.2" + } +} diff --git a/examples/json-api/src/index.ts b/examples/json-api/src/index.ts new file mode 100644 index 0000000..22dbd35 --- /dev/null +++ b/examples/json-api/src/index.ts @@ -0,0 +1,79 @@ +import assert from "node:assert"; +import { serve } from "@hono/node-server"; +import { connect, type Framer } from "framer-api"; +import { Hono } from "hono"; + +const projectUrl = process.env["EXAMPLE_PROJECT_URL"]; +assert(projectUrl, "EXAMPLE_PROJECT_URL environment variable is required"); + +interface Variables { + framer: Framer; +} + +const app = new Hono<{ Variables: Variables }>(); + +// Middleware to connect to Framer and set the framer client in the context. +// Will not share the instance between requests +app.use(async (c, next) => { + // The `using` keyword is used to ensure that the Framer client is closed after the block is executed. + // This will automatically end the connection when the request is complete + // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/using + using framer = await connect(projectUrl); + c.set("framer", framer); + await next(); +}); + +app.get("/collections", async (c) => { + const allCollections = await c.var.framer.getCollections(); + + const collections = allCollections.map((col) => ({ + id: col.id, + name: col.name, + })); + + return c.json({ collections }); +}); + +app.get("/collections/:collectionId", async (c) => { + const collectionId = c.req.param("collectionId"); + const allCollections = await c.var.framer.getCollections(); + const collection = allCollections.find((col) => col.id === collectionId); + + if (!collection) { + return c.json({ error: "Collection not found" }, 404); + } + + const allItems = await collection.getItems(); + + const items = allItems.map((item) => ({ + id: item.id, + slug: item.slug, + })); + + return c.json({ items }); +}); + +app.get("/collections/:collectionId/:itemId", async (c) => { + const collectionId = c.req.param("collectionId"); + const itemId = c.req.param("itemId"); + const allCollections = await c.var.framer.getCollections(); + const collection = allCollections.find((col) => col.id === collectionId); + + if (!collection) { + return c.json({ error: "Collection not found" }, 404); + } + + const allItems = await collection.getItems(); + + const item = allItems.find((i) => i.id === itemId); + + if (!item) { + return c.json({ error: "Item not found" }, 404); + } + + return c.json(item); +}); + +serve(app, (info) => { + console.log(`Server running at http://localhost:${info.port}`); +}); diff --git a/examples/json-api/tsconfig.json b/examples/json-api/tsconfig.json new file mode 100644 index 0000000..8cff07e --- /dev/null +++ b/examples/json-api/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["**/*.ts"] +} diff --git a/package-lock.json b/package-lock.json index 801d394..1349b99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "examples/csv-importer": { "version": "0.0.1", "dependencies": { - "framer-api": "^0.0.1-alpha.6", + "framer-api": "^0.0.1-beta.0", "papaparse": "^5.5.3", "typescript": "^5.9.3" }, @@ -24,18 +24,16 @@ "@types/papaparse": "^5.3.15" } }, - "examples/csv-importer/node_modules/framer-api": { - "version": "0.0.1-alpha.6", - "resolved": "https://registry.npmjs.org/framer-api/-/framer-api-0.0.1-alpha.6.tgz", - "integrity": "sha512-hN4HtQlJzJ14n/ZApWHc7g1bfg7LNOV3ZfrzYm4n1l67WWag2Q/FJJJF/xZ7n085JNsVzWWNt18D80JFiYi6Iw==", + "examples/json-api": { + "version": "0.0.1", "dependencies": { - "devalue": "^5.4.2", - "unenv": "^2.0.0-rc.24", - "ws": "^8.18.0" - }, - "peerDependencies": { - "react": "^18.2.0", + "@hono/node-server": "^1.14.1", + "framer-api": "^0.0.1-beta.0", + "hono": "^4.7.10", "typescript": "^5.9.3" + }, + "devDependencies": { + "@types/node": "^22.10.2" } }, "node_modules/@biomejs/biome": { @@ -201,6 +199,18 @@ "node": ">=14.21.3" } }, + "node_modules/@hono/node-server": { + "version": "1.19.7", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.7.tgz", + "integrity": "sha512-vUcD0uauS7EU2caukW8z5lJKtoGMokxNbJtBiwHgpqxEXokaHCBkQUmCHhjFB1VUTWdqj25QoMkMKzgjq+uhrw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, "node_modules/@types/node": { "version": "22.19.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", @@ -231,12 +241,40 @@ "integrity": "sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A==", "license": "MIT" }, + "node_modules/framer-api": { + "version": "0.0.1-beta.0", + "resolved": "https://registry.npmjs.org/framer-api/-/framer-api-0.0.1-beta.0.tgz", + "integrity": "sha512-rSTIX4KOCf9zRMGzeI92b/+dAG67JibFYQjTlJlG119fA4nfDlFWclZIxaBdFRnQZIckrlE+GHvcym2T+dCItQ==", + "dependencies": { + "devalue": "^5.4.2", + "unenv": "^2.0.0-rc.24", + "ws": "^8.18.0" + }, + "peerDependencies": { + "react": "^18.2.0", + "typescript": "^5.9.3" + } + }, + "node_modules/hono": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.1.tgz", + "integrity": "sha512-KsFcH0xxHes0J4zaQgWbYwmz3UPOOskdqZmItstUG93+Wk1ePBLkLGwbP9zlmh1BFUiL8Qp+Xfu9P7feJWpGNg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, + "node_modules/json-api": { + "resolved": "examples/json-api", + "link": true + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",