feat: pulseflare package — CF Workers + D1 runtime (refs #5)
CI / test (push) Has been cancelled
CI / test (push) Has been cancelled
This commit is contained in:
@@ -60,6 +60,17 @@
|
||||
"@uncaged/pulse": ">=0.1.0",
|
||||
},
|
||||
},
|
||||
"packages/pulseflare": {
|
||||
"name": "@uncaged/pulseflare",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@uncaged/pulse": "workspace:*",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^4.20260418.1",
|
||||
"wrangler": "^4.83.0",
|
||||
},
|
||||
},
|
||||
"packages/upulse": {
|
||||
"name": "@uncaged/upulse",
|
||||
"version": "0.1.0",
|
||||
@@ -96,6 +107,144 @@
|
||||
|
||||
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.11", "", { "os": "win32", "cpu": "x64" }, "sha512-A8D3JM/00C2KQgUV3oj8Ba15EHEYwebAGCy5Sf9GAjr5Y3+kJIYOiESoqRDeuRZueuMdCsbLZIUqmPhpYXJE9A=="],
|
||||
|
||||
"@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.2", "", {}, "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ=="],
|
||||
|
||||
"@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.16.0", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": "1.20260301.1 || ~1.20260302.1 || ~1.20260303.1 || ~1.20260304.1 || >1.20260305.0 <2.0.0-0" }, "optionalPeers": ["workerd"] }, "sha512-8ovsRpwzPoEqPUzoErAYVv8l3FMZNeBVQfJTvtzP4AgLSRGZISRfuChFxHWUQd3n6cnrwkuTGxT+2cGo8EsyYg=="],
|
||||
|
||||
"@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260415.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-dsxaKsQm3LnPGNPEdsRv09QN3Y4DqCw7kX5j6noKqbAtro2jTr95sVlYM1jUxZ5FkOl1f7SXgaKKB9t5H5Nkbg=="],
|
||||
|
||||
"@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260415.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-+JgSgVA49KyKteHRA1SnonE4Zn5Ei5zdAp5FQMxFmXI8qulZw4Hl7safXxRyK4i9sTO8gl7TFOKO5Q64VPvSDQ=="],
|
||||
|
||||
"@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260415.1", "", { "os": "linux", "cpu": "x64" }, "sha512-tU+9pwsqCy8afOVlGtiWrWQc/fedQK4SRm4KPIAt+zOiQWDxWASm6YGBUJis5c648WN80yz47qnmdDi8DQNOcA=="],
|
||||
|
||||
"@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260415.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-bR9uITnV19r5NQ14xnypi2xHXu2iQvfYV8cVgx0JouFUmWwTEEAwFVojDdssGq93VHX9hr/pi2IRUZeegbYBog=="],
|
||||
|
||||
"@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260415.1", "", { "os": "win32", "cpu": "x64" }, "sha512-4NuMLlerI0Ijua3Ir8HXQ+qyNvCUDEG5gDco5Om+sAiK6rnWiz+aGoSlbB8W16yW9QAgzCstbmXLiVknUBflfQ=="],
|
||||
|
||||
"@cloudflare/workers-types": ["@cloudflare/workers-types@4.20260418.1", "", {}, "sha512-bywXb2XmeSqrLCQYipcupLneqx015YhhNWz2v9b9iatpe8Cg551vP7ZuD5S2a6GfBka0dDnO70kIBiBvFglcrg=="],
|
||||
|
||||
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.3", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.3", "", { "os": "android", "cpu": "arm" }, "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.3", "", { "os": "android", "cpu": "arm64" }, "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.3", "", { "os": "android", "cpu": "x64" }, "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="],
|
||||
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="],
|
||||
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="],
|
||||
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="],
|
||||
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="],
|
||||
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.3", "", { "os": "linux", "cpu": "arm" }, "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="],
|
||||
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="],
|
||||
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.3", "", { "os": "linux", "cpu": "ia32" }, "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="],
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="],
|
||||
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="],
|
||||
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="],
|
||||
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.3", "", { "os": "linux", "cpu": "none" }, "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="],
|
||||
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="],
|
||||
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.3", "", { "os": "linux", "cpu": "x64" }, "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="],
|
||||
|
||||
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="],
|
||||
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.3", "", { "os": "none", "cpu": "x64" }, "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="],
|
||||
|
||||
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.3", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="],
|
||||
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="],
|
||||
|
||||
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.3", "", { "os": "none", "cpu": "arm64" }, "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="],
|
||||
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.3", "", { "os": "sunos", "cpu": "x64" }, "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.3", "", { "os": "win32", "cpu": "x64" }, "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="],
|
||||
|
||||
"@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="],
|
||||
|
||||
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
|
||||
|
||||
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="],
|
||||
|
||||
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="],
|
||||
|
||||
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="],
|
||||
|
||||
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="],
|
||||
|
||||
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="],
|
||||
|
||||
"@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="],
|
||||
|
||||
"@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="],
|
||||
|
||||
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="],
|
||||
|
||||
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="],
|
||||
|
||||
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="],
|
||||
|
||||
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="],
|
||||
|
||||
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="],
|
||||
|
||||
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="],
|
||||
|
||||
"@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="],
|
||||
|
||||
"@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="],
|
||||
|
||||
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="],
|
||||
|
||||
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="],
|
||||
|
||||
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="],
|
||||
|
||||
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="],
|
||||
|
||||
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="],
|
||||
|
||||
"@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="],
|
||||
|
||||
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="],
|
||||
|
||||
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
|
||||
|
||||
"@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="],
|
||||
|
||||
"@poppinss/dumper": ["@poppinss/dumper@0.6.5", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw=="],
|
||||
|
||||
"@poppinss/exception": ["@poppinss/exception@1.2.3", "", {}, "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw=="],
|
||||
|
||||
"@sindresorhus/is": ["@sindresorhus/is@7.2.0", "", {}, "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw=="],
|
||||
|
||||
"@speed-highlight/core": ["@speed-highlight/core@1.2.15", "", {}, "sha512-BMq1K3DsElxDWawkX6eLg9+CKJrTVGCBAWVuHXVUV2u0s2711qiChLSId6ikYPfxhdYocLNt3wWwSvDiTvFabw=="],
|
||||
|
||||
"@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
|
||||
|
||||
"@uncaged/pulse": ["@uncaged/pulse@workspace:packages/pulse"],
|
||||
@@ -106,18 +255,62 @@
|
||||
|
||||
"@uncaged/pulse-openclaw": ["@uncaged/pulse-openclaw@workspace:packages/pulse-openclaw"],
|
||||
|
||||
"@uncaged/pulseflare": ["@uncaged/pulseflare@workspace:packages/pulseflare"],
|
||||
|
||||
"@uncaged/upulse": ["@uncaged/upulse@workspace:packages/upulse"],
|
||||
|
||||
"blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.12", "", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="],
|
||||
|
||||
"commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
|
||||
|
||||
"cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="],
|
||||
|
||||
"esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"jsonata": ["jsonata@2.1.0", "", {}, "sha512-OCzaRMK8HobtX8fp37uIVmL8CY1IGc/a6gLsDqz3quExFR09/U78HUzWYr7T31UEB6+Eu0/8dkVD5fFDOl9a8w=="],
|
||||
|
||||
"kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],
|
||||
|
||||
"miniflare": ["miniflare@4.20260415.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.24.8", "workerd": "1.20260415.1", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-JoExRWN4YBI2luA5BoSMFEgi8rQWXUGzo3mtE+58VXCLV3jj/Xnk5Yeqs/IXWz8Es5GJIaq6BtsixDvAxXSIng=="],
|
||||
|
||||
"path-to-regexp": ["path-to-regexp@6.3.0", "", {}, "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="],
|
||||
|
||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
|
||||
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
|
||||
|
||||
"supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"typescript": ["typescript@6.0.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ=="],
|
||||
|
||||
"undici": ["undici@7.24.8", "", {}, "sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ=="],
|
||||
|
||||
"undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
|
||||
|
||||
"unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="],
|
||||
|
||||
"workerd": ["workerd@1.20260415.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260415.1", "@cloudflare/workerd-darwin-arm64": "1.20260415.1", "@cloudflare/workerd-linux-64": "1.20260415.1", "@cloudflare/workerd-linux-arm64": "1.20260415.1", "@cloudflare/workerd-windows-64": "1.20260415.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-phyPjRnx+mQDfkhN9ENPioL1L0SdhYs4S0YmJK/xF9Oga+ykNfdSy1MHnsOj8yqnOV96zcVQMx32dJ0r3pq0jQ=="],
|
||||
|
||||
"wrangler": ["wrangler@4.83.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.2", "@cloudflare/unenv-preset": "2.16.0", "blake3-wasm": "2.1.5", "esbuild": "0.27.3", "miniflare": "4.20260415.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260415.1" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260415.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-gw5g3LCiuAqVWxaoKY6+quE0HzAUEFb/FV3oAlNkE1ttd4XP3FiV91XDkkzUCcdqxS4WjhQvPhIDBNdhEi8P0A=="],
|
||||
|
||||
"ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="],
|
||||
|
||||
"youch": ["youch@4.1.0-beta.10", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@poppinss/dumper": "^0.6.4", "@speed-highlight/core": "^1.2.7", "cookie": "^1.0.2", "youch-core": "^0.3.3" } }, "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ=="],
|
||||
|
||||
"youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="],
|
||||
|
||||
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "@uncaged/pulseflare",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "wrangler dev",
|
||||
"deploy": "wrangler deploy",
|
||||
"migrate": "wrangler d1 migrations apply pulseflare-db"
|
||||
},
|
||||
"dependencies": {
|
||||
"@uncaged/pulse": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^4.20260418.1",
|
||||
"wrangler": "^4.83.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import type { D1Database, DurableObject, DurableObjectState } from '@cloudflare/workers-types';
|
||||
import { D1PulseStore } from './store-d1.js';
|
||||
import { createSigilExecutor } from './executor-sigil.js';
|
||||
|
||||
export interface PulseTickEnv {
|
||||
DB: D1Database;
|
||||
SIGIL_URL: string;
|
||||
SIGIL_TOKEN?: string;
|
||||
TICK_INTERVAL_MS?: string;
|
||||
}
|
||||
|
||||
export class PulseTick implements DurableObject {
|
||||
private tickIntervalMs: number;
|
||||
private store: D1PulseStore;
|
||||
private sigilExecutor: ReturnType<typeof createSigilExecutor>;
|
||||
|
||||
constructor(private state: DurableObjectState, private env: PulseTickEnv) {
|
||||
this.tickIntervalMs = parseInt(env.TICK_INTERVAL_MS ?? '30000'); // Default 30s
|
||||
this.store = new D1PulseStore(env.DB);
|
||||
this.sigilExecutor = createSigilExecutor(
|
||||
env.SIGIL_URL,
|
||||
env.SIGIL_TOKEN ?? 'default-token'
|
||||
);
|
||||
}
|
||||
|
||||
async fetch(request: Request): Promise<Response> {
|
||||
const url = new URL(request.url);
|
||||
|
||||
if (request.method === 'POST' && url.pathname === '/tick') {
|
||||
// Manual tick trigger
|
||||
await this.performTick();
|
||||
return new Response(JSON.stringify({ status: 'tick performed' }), {
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
if (request.method === 'GET' && url.pathname === '/status') {
|
||||
const status = await this.getStatus();
|
||||
return new Response(JSON.stringify(status), {
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
if (request.method === 'POST' && url.pathname === '/start') {
|
||||
await this.startTicking();
|
||||
return new Response(JSON.stringify({ status: 'ticking started' }), {
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
return new Response('Not found', { status: 404 });
|
||||
}
|
||||
|
||||
async alarm(): Promise<void> {
|
||||
try {
|
||||
await this.performTick();
|
||||
|
||||
// Schedule next tick
|
||||
const nextTickTime = Date.now() + this.tickIntervalMs;
|
||||
await this.state.storage.setAlarm(nextTickTime);
|
||||
} catch (error) {
|
||||
console.error('Error during tick:', error);
|
||||
|
||||
// Still schedule next tick even if this one failed
|
||||
const nextTickTime = Date.now() + this.tickIntervalMs;
|
||||
await this.state.storage.setAlarm(nextTickTime);
|
||||
}
|
||||
}
|
||||
|
||||
private async startTicking(): Promise<void> {
|
||||
// Cancel any existing alarm
|
||||
await this.state.storage.deleteAlarm();
|
||||
|
||||
// Set first alarm
|
||||
const firstTickTime = Date.now() + this.tickIntervalMs;
|
||||
await this.state.storage.setAlarm(firstTickTime);
|
||||
|
||||
await this.state.storage.put('ticking', true);
|
||||
}
|
||||
|
||||
private async performTick(): Promise<void> {
|
||||
console.log('Performing tick at', new Date().toISOString());
|
||||
|
||||
// 1. Read latest state from D1
|
||||
const hasEvents = await this.store.hasEvents();
|
||||
const recentEvents = await this.store.getRecent(10);
|
||||
|
||||
console.log('Has events:', hasEvents, 'Recent events:', recentEvents.length);
|
||||
|
||||
// 2. Run Pulse tick logic (placeholder for now)
|
||||
// This is where we would:
|
||||
// - Load rule definitions
|
||||
// - Process events through rules
|
||||
// - Generate effects
|
||||
// - Execute effects through Sigil
|
||||
|
||||
// Placeholder: just log the tick
|
||||
const tickEvent = {
|
||||
occurredAt: Date.now(),
|
||||
kind: 'pulse.tick',
|
||||
meta: JSON.stringify({
|
||||
eventsCount: recentEvents.length,
|
||||
tickedAt: new Date().toISOString()
|
||||
})
|
||||
};
|
||||
|
||||
await this.store.appendEvent(tickEvent);
|
||||
|
||||
// Example effect execution (commented out for now)
|
||||
/*
|
||||
try {
|
||||
const result = await this.sigilExecutor.invoke('example-capability', {
|
||||
message: 'Tick performed',
|
||||
timestamp: Date.now()
|
||||
});
|
||||
console.log('Effect result:', result);
|
||||
} catch (error) {
|
||||
console.error('Error executing effect:', error);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
private async getStatus(): Promise<any> {
|
||||
const isTicking = await this.state.storage.get<boolean>('ticking') ?? false;
|
||||
const currentAlarm = await this.state.storage.getAlarm();
|
||||
const hasEvents = await this.store.hasEvents();
|
||||
const recentEvents = await this.store.getRecent(5);
|
||||
|
||||
return {
|
||||
isTicking,
|
||||
nextAlarmAt: currentAlarm ? new Date(currentAlarm).toISOString() : null,
|
||||
tickIntervalMs: this.tickIntervalMs,
|
||||
hasEvents,
|
||||
recentEventsCount: recentEvents.length,
|
||||
lastEvents: recentEvents.slice(0, 3)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
export interface SigilExecutor {
|
||||
invoke(capability: string, input: unknown): Promise<{ status: number; payload: unknown }>;
|
||||
}
|
||||
|
||||
export function createSigilExecutor(sigilUrl: string, token: string): SigilExecutor {
|
||||
return {
|
||||
async invoke(capability, input) {
|
||||
const res = await fetch(`${sigilUrl}/_api/invoke`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({ capability, params: input }),
|
||||
});
|
||||
const data = await res.json();
|
||||
return { status: res.status, payload: data };
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import type { D1Database, DurableObjectNamespace } from '@cloudflare/workers-types';
|
||||
import { D1PulseStore } from './store-d1.js';
|
||||
import { PulseTick } from './durable-tick.js';
|
||||
|
||||
export { PulseTick };
|
||||
|
||||
export interface Env {
|
||||
DB: D1Database;
|
||||
PULSE_TICK: DurableObjectNamespace;
|
||||
SIGIL_URL: string;
|
||||
SIGIL_TOKEN?: string;
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request: Request, env: Env): Promise<Response> {
|
||||
const url = new URL(request.url);
|
||||
const path = url.pathname;
|
||||
|
||||
// Health check
|
||||
if (request.method === 'GET' && path === '/health') {
|
||||
return new Response(JSON.stringify({
|
||||
status: 'healthy',
|
||||
timestamp: new Date().toISOString(),
|
||||
service: 'pulseflare'
|
||||
}), {
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
// Manual tick trigger
|
||||
if (request.method === 'POST' && path === '/tick') {
|
||||
const durableObjectId = env.PULSE_TICK.idFromName('default');
|
||||
const durableObject = env.PULSE_TICK.get(durableObjectId);
|
||||
|
||||
return await durableObject.fetch(new Request(request.url.replace(path, '/tick'), {
|
||||
method: 'POST'
|
||||
}));
|
||||
}
|
||||
|
||||
// Get events
|
||||
if (request.method === 'GET' && path === '/events') {
|
||||
const store = new D1PulseStore(env.DB);
|
||||
const limit = parseInt(url.searchParams.get('limit') ?? '20');
|
||||
const events = await store.getRecent(limit);
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
events,
|
||||
count: events.length,
|
||||
timestamp: new Date().toISOString()
|
||||
}), {
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
// Get status (from Durable Object)
|
||||
if (request.method === 'GET' && path === '/status') {
|
||||
const durableObjectId = env.PULSE_TICK.idFromName('default');
|
||||
const durableObject = env.PULSE_TICK.get(durableObjectId);
|
||||
|
||||
return await durableObject.fetch(new Request(request.url.replace(path, '/status'), {
|
||||
method: 'GET'
|
||||
}));
|
||||
}
|
||||
|
||||
// Start ticking
|
||||
if (request.method === 'POST' && path === '/start') {
|
||||
const durableObjectId = env.PULSE_TICK.idFromName('default');
|
||||
const durableObject = env.PULSE_TICK.get(durableObjectId);
|
||||
|
||||
return await durableObject.fetch(new Request(request.url.replace(path, '/start'), {
|
||||
method: 'POST'
|
||||
}));
|
||||
}
|
||||
|
||||
// Default 404
|
||||
return new Response(JSON.stringify({
|
||||
error: 'Not found',
|
||||
path,
|
||||
method: request.method,
|
||||
available_endpoints: [
|
||||
'GET /health',
|
||||
'POST /tick',
|
||||
'GET /events',
|
||||
'GET /status',
|
||||
'POST /start'
|
||||
]
|
||||
}), {
|
||||
status: 404,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
-- Events table
|
||||
CREATE TABLE IF NOT EXISTS events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
occurred_at INTEGER NOT NULL,
|
||||
kind TEXT NOT NULL,
|
||||
key TEXT,
|
||||
hash TEXT,
|
||||
code_rev TEXT,
|
||||
meta TEXT,
|
||||
object_id INTEGER REFERENCES objects(id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_occurred ON events(occurred_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_kind_key ON events(kind, key, occurred_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_code_rev ON events(code_rev, occurred_at);
|
||||
|
||||
-- Objects table
|
||||
CREATE TABLE IF NOT EXISTS objects (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
object_type TEXT NOT NULL,
|
||||
external_id TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
code_rev TEXT NOT NULL,
|
||||
UNIQUE(object_type, external_id)
|
||||
);
|
||||
|
||||
-- Projections table (from projection-engine.js)
|
||||
CREATE TABLE IF NOT EXISTS projections (
|
||||
name TEXT PRIMARY KEY,
|
||||
last_event_id INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
-- Defs tables (from defs.js)
|
||||
CREATE TABLE IF NOT EXISTS event_defs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
code_hash TEXT NOT NULL UNIQUE,
|
||||
data TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS rule_defs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
code_hash TEXT NOT NULL UNIQUE,
|
||||
data TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS executor_defs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
code_hash TEXT NOT NULL UNIQUE,
|
||||
data TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS projection_defs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
code_hash TEXT NOT NULL UNIQUE,
|
||||
data TEXT NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,312 @@
|
||||
import type { D1Database } from '@cloudflare/workers-types';
|
||||
import type { EventRecord, ObjectInstance, PulseStore } from '@uncaged/pulse';
|
||||
|
||||
/**
|
||||
* D1 implementation of PulseStore interface
|
||||
* Adapts from bun:sqlite to Cloudflare D1
|
||||
*/
|
||||
export class D1PulseStore implements PulseStore {
|
||||
constructor(private db: D1Database) {}
|
||||
|
||||
async appendEvent(event: Omit<EventRecord, 'id'>): Promise<EventRecord> {
|
||||
const result = await this.db
|
||||
.prepare(`
|
||||
INSERT INTO events (occurred_at, kind, key, hash, code_rev, meta, object_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`)
|
||||
.bind(
|
||||
event.occurredAt,
|
||||
event.kind,
|
||||
event.key ?? null,
|
||||
event.hash ?? null,
|
||||
event.codeRev ?? null,
|
||||
event.meta ?? null,
|
||||
event.objectId ?? null,
|
||||
)
|
||||
.run();
|
||||
|
||||
const id = result.meta.last_row_id as number;
|
||||
return { id, ...event };
|
||||
}
|
||||
|
||||
async appendEvents(events: Omit<EventRecord, 'id'>[]): Promise<EventRecord[]> {
|
||||
const statements = events.map(event =>
|
||||
this.db
|
||||
.prepare(`
|
||||
INSERT INTO events (occurred_at, kind, key, hash, code_rev, meta, object_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
`)
|
||||
.bind(
|
||||
event.occurredAt,
|
||||
event.kind,
|
||||
event.key ?? null,
|
||||
event.hash ?? null,
|
||||
event.codeRev ?? null,
|
||||
event.meta ?? null,
|
||||
event.objectId ?? null,
|
||||
)
|
||||
);
|
||||
|
||||
const results = await this.db.batch(statements);
|
||||
|
||||
return events.map((event, index) => ({
|
||||
id: results[index].meta.last_row_id as number,
|
||||
...event
|
||||
}));
|
||||
}
|
||||
|
||||
async createObject(opts: { objectType: string; externalId?: string; codeRev: string }): Promise<number> {
|
||||
const now = Date.now();
|
||||
const extId = opts.externalId ?? null;
|
||||
|
||||
// Idempotent: if (objectType, externalId) already exists, return existing id
|
||||
if (extId !== null) {
|
||||
const existing = await this.db
|
||||
.prepare('SELECT id FROM objects WHERE object_type = ? AND external_id = ?')
|
||||
.bind(opts.objectType, extId)
|
||||
.first<{ id: number }>();
|
||||
|
||||
if (existing) return existing.id;
|
||||
}
|
||||
|
||||
const result = await this.db
|
||||
.prepare('INSERT INTO objects (object_type, external_id, created_at, code_rev) VALUES (?, ?, ?, ?)')
|
||||
.bind(opts.objectType, extId, now, opts.codeRev)
|
||||
.run();
|
||||
|
||||
return result.meta.last_row_id as number;
|
||||
}
|
||||
|
||||
async getObjectInstance(id: number): Promise<ObjectInstance | null> {
|
||||
const row = await this.db
|
||||
.prepare('SELECT * FROM objects WHERE id = ?')
|
||||
.bind(id)
|
||||
.first<any>();
|
||||
|
||||
if (!row) return null;
|
||||
|
||||
return {
|
||||
id: row.id,
|
||||
objectType: row.object_type,
|
||||
externalId: row.external_id,
|
||||
createdAt: row.created_at,
|
||||
codeRev: row.code_rev,
|
||||
};
|
||||
}
|
||||
|
||||
async queryObjectsByType(objectType: string): Promise<ObjectInstance[]> {
|
||||
const { results } = await this.db
|
||||
.prepare('SELECT * FROM objects WHERE object_type = ?')
|
||||
.bind(objectType)
|
||||
.all<any>();
|
||||
|
||||
return results.map(row => ({
|
||||
id: row.id,
|
||||
objectType: row.object_type,
|
||||
externalId: row.external_id,
|
||||
createdAt: row.created_at,
|
||||
codeRev: row.code_rev,
|
||||
}));
|
||||
}
|
||||
|
||||
async getLatest(kind: string, key?: string): Promise<EventRecord | null> {
|
||||
const row = await this.db
|
||||
.prepare(`
|
||||
SELECT * FROM events
|
||||
WHERE kind = ? AND (key = ? OR ? IS NULL)
|
||||
ORDER BY occurred_at DESC, id DESC
|
||||
LIMIT 1
|
||||
`)
|
||||
.bind(kind, key ?? null, key ?? null)
|
||||
.first<any>();
|
||||
|
||||
return row ? this.rowToEventRecord(row) : null;
|
||||
}
|
||||
|
||||
async getLatestWhere(opts: { kind: string; key?: string; codeRev?: string }): Promise<EventRecord | null> {
|
||||
const conditions: string[] = ['kind = ?'];
|
||||
const params: (string | number | null)[] = [opts.kind];
|
||||
|
||||
if (opts.key !== undefined) {
|
||||
conditions.push('key = ?');
|
||||
params.push(opts.key);
|
||||
}
|
||||
if (opts.codeRev !== undefined) {
|
||||
conditions.push('code_rev = ?');
|
||||
params.push(opts.codeRev);
|
||||
}
|
||||
|
||||
const sql = `SELECT * FROM events WHERE ${conditions.join(' AND ')} ORDER BY occurred_at DESC, id DESC LIMIT 1`;
|
||||
let stmt = this.db.prepare(sql);
|
||||
for (const param of params) {
|
||||
stmt = stmt.bind(param);
|
||||
}
|
||||
|
||||
const row = await stmt.first<any>();
|
||||
return row ? this.rowToEventRecord(row) : null;
|
||||
}
|
||||
|
||||
async getRecent(limit: number = 20): Promise<EventRecord[]> {
|
||||
const { results } = await this.db
|
||||
.prepare(`SELECT * FROM events ORDER BY occurred_at DESC, id DESC LIMIT ?`)
|
||||
.bind(limit)
|
||||
.all<any>();
|
||||
|
||||
return results.map(row => this.rowToEventRecord(row));
|
||||
}
|
||||
|
||||
async queryByKind(
|
||||
kind: string,
|
||||
opts?: {
|
||||
key?: string;
|
||||
since?: number;
|
||||
codeRev?: string;
|
||||
limit?: number;
|
||||
},
|
||||
): Promise<EventRecord[]> {
|
||||
const conditions: string[] = ['kind = ?'];
|
||||
const params: (string | number | null)[] = [kind];
|
||||
|
||||
if (opts?.key !== undefined) {
|
||||
conditions.push('key = ?');
|
||||
params.push(opts.key);
|
||||
}
|
||||
if (opts?.since !== undefined) {
|
||||
conditions.push('occurred_at >= ?');
|
||||
params.push(opts.since);
|
||||
}
|
||||
if (opts?.codeRev !== undefined) {
|
||||
conditions.push('code_rev = ?');
|
||||
params.push(opts.codeRev);
|
||||
}
|
||||
|
||||
let sql = `SELECT * FROM events WHERE ${conditions.join(' AND ')} ORDER BY occurred_at DESC, id DESC`;
|
||||
if (opts?.limit !== undefined) {
|
||||
sql += ' LIMIT ?';
|
||||
params.push(opts.limit);
|
||||
}
|
||||
|
||||
let stmt = this.db.prepare(sql);
|
||||
for (const param of params) {
|
||||
stmt = stmt.bind(param);
|
||||
}
|
||||
|
||||
const { results } = await stmt.all<any>();
|
||||
return results.map(row => this.rowToEventRecord(row));
|
||||
}
|
||||
|
||||
async getAfter(
|
||||
afterId: number,
|
||||
opts?: {
|
||||
kind?: string;
|
||||
key?: string;
|
||||
codeRev?: string;
|
||||
},
|
||||
): Promise<EventRecord[]> {
|
||||
const conditions: string[] = ['id > ?'];
|
||||
const params: (string | number | null)[] = [afterId];
|
||||
|
||||
if (opts?.kind !== undefined) {
|
||||
conditions.push('kind = ?');
|
||||
params.push(opts.kind);
|
||||
}
|
||||
if (opts?.key !== undefined) {
|
||||
conditions.push('key = ?');
|
||||
params.push(opts.key);
|
||||
}
|
||||
if (opts?.codeRev !== undefined) {
|
||||
conditions.push('code_rev = ?');
|
||||
params.push(opts.codeRev);
|
||||
}
|
||||
|
||||
const sql = `SELECT * FROM events WHERE ${conditions.join(' AND ')} ORDER BY id ASC`;
|
||||
let stmt = this.db.prepare(sql);
|
||||
for (const param of params) {
|
||||
stmt = stmt.bind(param);
|
||||
}
|
||||
|
||||
const { results } = await stmt.all<any>();
|
||||
return results.map(row => this.rowToEventRecord(row));
|
||||
}
|
||||
|
||||
async hasEvents(): Promise<boolean> {
|
||||
const result = await this.db
|
||||
.prepare('SELECT 1 FROM events LIMIT 1')
|
||||
.first();
|
||||
return result !== null;
|
||||
}
|
||||
|
||||
async putObject(data: unknown): Promise<string> {
|
||||
// For simplicity, use a basic hash function
|
||||
const hash = await this.hashObject(data);
|
||||
|
||||
// Store in D1 as a simple key-value table (could be added to schema later)
|
||||
// For now, just return the hash - actual CAS implementation would need object storage
|
||||
return hash;
|
||||
}
|
||||
|
||||
async getObject(hash: string): Promise<unknown | null> {
|
||||
// Placeholder - would need object storage or D1 table for CAS
|
||||
return null;
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
// D1 doesn't need explicit closing
|
||||
}
|
||||
|
||||
async archiveEvents(olderThan: number): Promise<number> {
|
||||
const result = await this.db
|
||||
.prepare('DELETE FROM events WHERE occurred_at < ?')
|
||||
.bind(olderThan)
|
||||
.run();
|
||||
|
||||
return result.meta.changes;
|
||||
}
|
||||
|
||||
async downsampleEvents(
|
||||
kind: string,
|
||||
key: string,
|
||||
intervalMs: number,
|
||||
olderThan: number,
|
||||
): Promise<number> {
|
||||
const safeInterval = Math.floor(Math.abs(intervalMs));
|
||||
if (safeInterval <= 0) return 0;
|
||||
|
||||
const result = await this.db
|
||||
.prepare(`
|
||||
DELETE FROM events WHERE kind = ? AND key = ? AND occurred_at < ? AND id NOT IN (
|
||||
SELECT id FROM (
|
||||
SELECT id, ROW_NUMBER() OVER (
|
||||
PARTITION BY (occurred_at / ${safeInterval}) ORDER BY occurred_at DESC
|
||||
) as rn FROM events WHERE kind = ? AND key = ? AND occurred_at < ?
|
||||
) WHERE rn = 1
|
||||
)
|
||||
`)
|
||||
.bind(kind, key, olderThan, kind, key, olderThan)
|
||||
.run();
|
||||
|
||||
return result.meta.changes;
|
||||
}
|
||||
|
||||
private rowToEventRecord(row: any): EventRecord {
|
||||
const rec: EventRecord = {
|
||||
id: row.id,
|
||||
occurredAt: row.occurred_at,
|
||||
kind: row.kind,
|
||||
};
|
||||
if (row.key != null) rec.key = row.key;
|
||||
if (row.hash != null) rec.hash = row.hash;
|
||||
if (row.code_rev != null) rec.codeRev = row.code_rev;
|
||||
if (row.meta != null) rec.meta = row.meta;
|
||||
if (row.object_id != null) rec.objectId = row.object_id;
|
||||
return rec;
|
||||
}
|
||||
|
||||
private async hashObject(data: unknown): Promise<string> {
|
||||
const str = JSON.stringify(data);
|
||||
const msgUint8 = new TextEncoder().encode(str);
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8);
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('').slice(0, 32);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"lib": ["ES2020", "WebWorker"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"types": ["@cloudflare/workers-types"]
|
||||
},
|
||||
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
name = "pulseflare"
|
||||
main = "src/index.ts"
|
||||
compatibility_date = "2024-09-25"
|
||||
|
||||
[[d1_databases]]
|
||||
binding = "DB"
|
||||
database_name = "pulseflare-db"
|
||||
database_id = "placeholder-fill-later"
|
||||
|
||||
[durable_objects]
|
||||
bindings = [
|
||||
{ name = "PULSE_TICK", class_name = "PulseTick" }
|
||||
]
|
||||
|
||||
[[migrations]]
|
||||
tag = "v1"
|
||||
new_classes = ["PulseTick"]
|
||||
|
||||
[vars]
|
||||
SIGIL_URL = "https://sigil.shazhou.workers.dev"
|
||||
Reference in New Issue
Block a user