diff --git a/package.json b/package.json index e8b570b..61b06ee 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "prepare-s3-test": "docker run -d -p 9000:9000 --name minio -e MINIO_ACCESS_KEY=minioadmin -e MINIO_SECRET_KEY=minioadmin -v /tmp/data:/data -v /tmp/config:/root/.minio minio/minio server /data", "setup-gateway-servers": "pnpm run '/^pretest:/'", "pretest:partykit": "cd tests/connect-partykit/app && pnpm run dev", - "pretest:netlify": "cd tests/connect-netlify/app && pnpm run copy-server && pnpm --package=netlify-cli dlx netlify dev --no-open", + "pretest:netlify": "cd tests/connect-netlify/app && pnpm run copy-server && pnpm --package=netlify-cli dlx netlify --version && pnpm --package=netlify-cli dlx netlify dev --no-open", "test-gateways": "vitest --run connector gateway", "test": "vitest --run", "stop-s3-test": "docker rm -f minio", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7c673f7..b96d8ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -858,7 +858,7 @@ packages: engines: {node: '>=14'} '@fireproof/core@file:../fireproof/dist/fireproof-core/fireproof-core-0.0.0-smoke.tgz': - resolution: {integrity: sha512-iYLnSmmfb9NmWdZavDqm07Q/rhR6nSy8XyR6LeIN+BuMfQ/7FM1OimJsWv5EuUAcV6V1+dM/66p5AMO4CIG2ZA==, tarball: file:../fireproof/dist/fireproof-core/fireproof-core-0.0.0-smoke.tgz} + resolution: {integrity: sha512-Act6RZ3KWIcpBhMmCcrlAbCTdR5ySdHDmgdRP7awetGVWVndnT3rfyHPIz/t5s/o4fK839qQoBcVS34BLCSYaA==, tarball: file:../fireproof/dist/fireproof-core/fireproof-core-0.0.0-smoke.tgz} version: 0.0.0-smoke peerDependencies: react: '>=18.0.0' @@ -4273,7 +4273,7 @@ snapshots: multiformats: 13.3.0 murmurhash3js-revisited: 3.0.0 - '@netlify/blobs@7.4.0': {} + '@netlify/blobs@8.1.0': {} '@noble/curves@1.6.0': dependencies: diff --git a/tests/connect-netlify/app/.gitignore b/tests/connect-netlify/app/.gitignore index 40bbae1..e9976d3 100644 --- a/tests/connect-netlify/app/.gitignore +++ b/tests/connect-netlify/app/.gitignore @@ -4,3 +4,5 @@ node_modules .netlify *.iife.js *.global.js +*.js.map +netlify/edge-functions/fireproof.ts diff --git a/tests/connect-netlify/app/netlify.toml b/tests/connect-netlify/app/netlify.toml new file mode 100644 index 0000000..f0d0333 --- /dev/null +++ b/tests/connect-netlify/app/netlify.toml @@ -0,0 +1,8 @@ +[build] + command = "# no build command" + publish = "" + +[dev] + functions = "netlify/edge-functions" + port = 8888 + publish = "public" diff --git a/tests/connect-netlify/app/netlify/edge-functions/fireproof.ts b/tests/connect-netlify/app/netlify/edge-functions/fireproof.ts deleted file mode 100644 index 831b115..0000000 --- a/tests/connect-netlify/app/netlify/edge-functions/fireproof.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { getStore } from "@netlify/blobs"; - -interface CRDTEntry { - readonly data: string; - readonly cid: string; - readonly parents: string[]; -} - -export default async (req: Request) => { - const url = new URL(req.url); - const carId = url.searchParams.get("car"); - const metaDb = url.searchParams.get("meta"); - - if (req.method === "PUT") { - if (carId) { - const carFiles = getStore("cars"); - const carArrayBuffer = new Uint8Array(await req.arrayBuffer()); - await carFiles.set(carId, carArrayBuffer); - return new Response(JSON.stringify({ ok: true }), { status: 201 }); - } else if (metaDb) { - const meta = getStore("meta"); - const x = await req.json(); - // fixme, marty changed to [0] as it is a slice of the structure we expected - const { data, cid, parents } = x[0] as CRDTEntry; - await meta.setJSON(`${metaDb}/${cid}`, { data, parents }); - return new Response(JSON.stringify({ ok: true }), { status: 201 }); - } - } else if (req.method === "GET") { - if (carId) { - const carFiles = getStore("cars"); - const carArrayBuffer = await carFiles.get(carId, { type: "arrayBuffer" }); - return new Response(carArrayBuffer, { status: 200 }); - } else if (metaDb) { - // Problem: Deletion operations are faster than write operations, leading to an empty list most of the time if deletes happen at PUT time. - // Solution: Delay deletes until GET operation. Utilize the parents list during read operation to mask and delete outdated entries. - const meta = getStore("meta"); - const { blobs } = await meta.list({ prefix: `${metaDb}/` }); - const allParents = [] as string[]; - const entries = ( - await Promise.all( - blobs.map(async (blob) => { - const blobContents = await meta.get(blob.key, { - type: "json", - }); - if (!blobContents) { - return { cid: blob.key.split("/")[1], data: null }; - } - const { data, parents } = blobContents; - if (parents) { - for (const p of parents) { - allParents.push(p.toString()); - void meta.delete(`${metaDb}/${p}`); - } - } - return { cid: blob.key.split("/")[1], data, parents }; - }) - ) - ).filter((entry) => entry.data !== null && !allParents.includes(entry.cid)); - return new Response(JSON.stringify(entries), { status: 200 }); - } - } else if (req.method === "DELETE") { - if (carId) { - const carFiles = getStore("cars"); - await carFiles.delete(carId); - return new Response(JSON.stringify({ ok: true }), { status: 200 }); - } else if (metaDb) { - const meta = getStore("meta"); - const { blobs } = await meta.list({ prefix: `${metaDb}/` }); - await Promise.all(blobs.map((blob) => meta.delete(blob.key))); - return new Response(JSON.stringify({ ok: true }), { status: 200 }); - } else { - const meta = getStore("meta"); - const { blobs } = await meta.list({ prefix: `main/` }); - for (const blob of blobs) { - await meta.delete(blob.key); - } - return new Response(JSON.stringify({ ok: true }), { status: 200 }); - } - } - - return new Response(JSON.stringify({ error: "Invalid path" }), { - status: 400, - }); -}; - -export const config = { path: "/fireproof" }; diff --git a/tests/connect-netlify/app/package.json b/tests/connect-netlify/app/package.json index 94b6bc6..ac84223 100644 --- a/tests/connect-netlify/app/package.json +++ b/tests/connect-netlify/app/package.json @@ -6,10 +6,11 @@ "main": "index.html", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "copy-server": "cp ../../../src/netlify/server.ts netlify/edge-functions/fireproof.ts", - "watch-server": "chokidar \"../../../src/netlify/server.ts\" -c \"npm run copy-server\"", - "predev": "npm run copy-server && npm run watch-server &", - "dev": "npx netlify dev --no-open" + "copy-server": "mkdir -p netlify/edge-functions && cp ../../../src/netlify/server.ts netlify/edge-functions/fireproof.ts", + "watch-server": "chokidar \"../../../src/netlify/server.ts\" -c \"pnpm run copy-server\"", + "watch": "pnpm run copy-server && pnpm run watch-server &", + "watch-dev": "pnpm run watch & pnpm run dev", + "dev": "pnpm --package=netlify-cli dlx netlify --version && pnpm --package=netlify-cli dlx netlify dev --no-open" }, "author": "", "license": "ISC", diff --git a/tests/connect-netlify/app/public/index.html b/tests/connect-netlify/app/public/index.html new file mode 100644 index 0000000..b8db318 --- /dev/null +++ b/tests/connect-netlify/app/public/index.html @@ -0,0 +1,271 @@ + + + + + + Fireproof Test + + + + + + +

Fireproof Todos

+ List: + + + +

+ Fireproof stores data locally and encrypts it before sending it to the cloud. This demo uses + Netlify Edge Functions, but you can easily run Fireproof on S3 or another provider. You also + accelerate sync times by using a real-time adapter like PartyKit or WebRTC, this demo polls + the server every 3 seconds. + Learn more in the Fireproof developer docs or + fork the app here. +

+ + + +
+ +
+ + +

Todos

+ + + + + diff --git a/tests/connect-partykit/app/.gitignore b/tests/connect-partykit/app/.gitignore new file mode 100644 index 0000000..664ad9a --- /dev/null +++ b/tests/connect-partykit/app/.gitignore @@ -0,0 +1 @@ +src/server.ts \ No newline at end of file