{"_id":"l-fen","_rev":"35-f9c950c8493ba52d06eaebbf0b65101a","name":"l-fen","dist-tags":{"latest":"1.1.4","alpha":"1.1.0-alpha.19","beta":"1.2.0-beta.4"},"versions":{"1.0.0":{"name":"l-fen","version":"1.0.0","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^14.14.35","@types/supertest":"^2.0.12","@types/yargs":"^16.0.0","@typescript-eslint/eslint-plugin":"^4.19.0","@typescript-eslint/parser":"^4.19.0","eslint":"^7.22.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^10.5.4","prettier":"^2.2.1","semantic-release":"^19.0.5","simple-git-hooks":"^2.2.0","typescript":"^4.2.3"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^7.1.6","google-gax":"^3.5.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","winston":"^3.8.2","yargs":"^16.2.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"gitHead":"33df655a45707a34cedb0a5ad268ce2e0fa36820","_id":"l-fen@1.0.0","_nodeVersion":"16.17.1","_npmVersion":"8.19.2","dist":{"integrity":"sha512-TEMcOoypi33kGe0/7sjTdyU7sU0BZcYDfrZOCvvJQ03kGqRBchwu5NM9hY8i+REA/JjTH8pF+nRSm1mWDKZQ9A==","shasum":"57de8746f14624648869ff46c041ba2704f926ca","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.0.0.tgz","fileCount":70,"unpackedSize":1180426,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIEKoJTRp6LVI3pjVeG1Ah2KXEd1ygDfrUcDcfGHk3NmBAiAZoegxrrz7RDMu+nQ+gUiYrKvkE5osrX7ydAyYrUmGqg=="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjWpDlACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmq9kg/9E4fALbd2EkJl3lw41CthVNVbDT5qfvikaPoJGzIHbfUAFQTM\r\ngjP8S8ReWPQJhq9DNTg1ylNRPxMnO//jhF6MFRE/i/a5BTLPub1+UYOHbW/L\r\nvYuQjvgo801ic3zSZQr5fTfTP8yUaJi+wuBP7A2UIWmoHmK9G/JXUv3Ahgo6\r\nbBeDLhN+D85BmGXJLwfBryHkGkxnlFgb4mqsuzkzyA6kKUVarLqb4CN6G4Ol\r\nNdHcqJRvcUTNSqmVPX+NHk0JVPjWaD44IlDPcgta6mUldvIUzo9Qo5SpTSNM\r\nKKDPmH7+pOD3hMd68fChgkSm8Ec9KlpasxAGw/97vraOmFU/g9ndZ5776Gic\r\nFU4s0QrtEHoKDnolDUVaUgT+GGyGFV6bLlIXMdH4Ir49HzLl/q8YjhC1Zoi7\r\nQ3gxMyNWFb04R6eyRfVS3b42gw9S0iic3JPMHtUSEGL381Rcqn1B3zntLHpM\r\nT62TnP6vtTLLzUWXrJedRVl58H75T4mOCWjKA3sId+3zITbe7Ed6g0OspYsw\r\nwRE9nPlhIv2XxgDEFHZyZ6lxQ7q40FJwGTLq1xItgJoBmA/aFHK4VfzmZu8c\r\nJ+hY/xXOAmIRijWZ2vYATa9fM0z0brWxUK4c50GpL0R8kUvM3zuswCDOsLIx\r\npk3lW2Y2J2ftx/H6EpcoFyufl1X6WdUTzz8=\r\n=+x07\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.0.0_1666879716875_0.8079684862985814"},"_hasShrinkwrap":false},"1.0.1":{"name":"l-fen","version":"1.0.1","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^14.14.35","@types/supertest":"^2.0.12","@types/yargs":"^16.0.0","@typescript-eslint/eslint-plugin":"^4.19.0","@typescript-eslint/parser":"^4.19.0","eslint":"^7.22.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^10.5.4","prettier":"^2.2.1","semantic-release":"^19.0.5","simple-git-hooks":"^2.2.0","typescript":"^4.2.3"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^7.1.6","google-gax":"^3.5.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","winston":"^3.8.2","yargs":"^16.2.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"gitHead":"b1e1173272877b29e6f16b31580239aa7d0cc11f","_id":"l-fen@1.0.1","_nodeVersion":"16.17.1","_npmVersion":"8.19.2","dist":{"integrity":"sha512-WC9LeZiMTwZ/HD/sJcMi3qOvpJJgQqD2DH0+Yv3L3kFp8iQWPaI9Ah7zNGDk81Yjn/jByKoByvLuBE+cjC6hCg==","shasum":"39095a030be3a8a724ec0b93d39aba6b2b984633","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.0.1.tgz","fileCount":70,"unpackedSize":1180690,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIDE06aU6YnPlLRScGpAVveGMe/kS0U3TipXG2URZTq69AiAn+D7UIvcKdpQJt47sYncb77xoHT98AHwACMnvVk2BPw=="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjXkg+ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpZmg/+KR/wjMlhTi7RUihkpuxJ6KMg1P9irYVYr4AyL52EL+XhDxeB\r\ncSn1Ag+k9ZIE5qLpnUxCsuRvNDd+MecRGRMV9hOjdH8wOByhceKI6XpOuR51\r\nWncJrA6euypvgG4+bfbV4dCTRwr8dhEXIPoHOcampNBY/U22tcSBQPMeiyLV\r\nqxoq6/2UlX+78GsrBcCKaqoma3WdxDhfY9qhrxtdtSq72qHkruIad27tC9iq\r\nTfOts7GHLHits6td1PBolz/FQBQi3y0QGvz8oH6WgV6sGKTdXZflqMp72rPm\r\nVHXSC5Za1AlqRfhHv82fwLsqqscPBnfPBQmTldxGXIVPnVqw0q+NqS9XvkLA\r\nQDE2mkVITKZjjfefn/J2ha9Cbfw6w1EgpLGScl27pD2Ns4lFMfBkZ0io+BX0\r\nPjYnE7zhKSfDLkH++GNBMeyt/fzhEx58SKjgvRw43TOMKcdL/AXAzRr5MWYD\r\n+EMo7wjtrvZy8ogoRlcfoyly5SAqT0+A9O4W1JLsEk5zHLQ5t9EyacuI3K8J\r\ncjo7RGKiF66LC6cUJhfZo2UpWf3ihIZI73+2B1p3FpczNvolGKB5nkNXGXjU\r\nYCPjIOU+Xlam5RjgT/BdjQ82POsJrJcxbuFj9hpXKFwyOh5xORLwQ0WZ6AH0\r\nizb9Z+BOQjVKYYmd/WQ27kGXo1kNSva1Vf4=\r\n=Gz01\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.0.1_1667123262039_0.967398425481347"},"_hasShrinkwrap":false},"1.0.2":{"name":"l-fen","version":"1.0.2","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^14.14.35","@types/supertest":"^2.0.12","@types/yargs":"^16.0.0","@typescript-eslint/eslint-plugin":"^4.19.0","@typescript-eslint/parser":"^4.19.0","eslint":"^7.22.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^10.5.4","prettier":"^2.2.1","semantic-release":"^19.0.5","simple-git-hooks":"^2.2.0","typescript":"^4.2.3"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^7.1.6","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","winston":"^3.8.2","yargs":"^16.2.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"gitHead":"2b0d6d71803d30e120a592de22e10f7d7cfc7111","_id":"l-fen@1.0.2","_nodeVersion":"16.17.1","_npmVersion":"8.19.2","dist":{"integrity":"sha512-1iREEeKwBJabEoUQLemKTyaudwaatn/ufq7XElxSZF1cT42NTDgtNtMqbo0tynXnXLb3XPZsgIiRv1/sKYn8Vg==","shasum":"90b1161d291dbcf9d7eb8f5baf770ece6a98d37c","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.0.2.tgz","fileCount":70,"unpackedSize":1180962,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIHLTzDOj03cLhN1PUfOhQgqf6ERSAaIFavZMBXZuqc5tAiACSolrTSjDyJnYn+ADWOekgyl9hf9cvEwjjtuQw7aluA=="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjXlK0ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmrmDhAAiA2y9m1RnXdVyayWZJCotqGu5W+wmTZ1Zzy7Pj3xVEoFH182\r\nyRVex8idkI32D3Z/ShU00wWZkWaQC5oGlUpKMnLpRSFqcy2ms6DDuLWpYI1U\r\np4ltiTwtJVoodrjxuQQXAKe5SR42xNgflFUZp9pLDD8Bc/MSM8jRpFZl1iDm\r\nEN5hBNzLRyFfOp4E624xAcwUJ3GwmkVpzvn8FtSJbeOkagncGd/uiZET3iEo\r\n/MiseGx5cYdOTkI3my8+VpqOQWPxtEUaQjlsfYDxkJUbY/1eIBeSbkdlsxmH\r\nBFrwDpi+CKDKGKiW7McSId47JZM5IxPRatxYeUIyMnA+HyGhkjwOrkWM5h4S\r\nbqzrY/KhwDAd43WUdAokjgICixev2D2PsfgUO5m7CD0bQSudoDXD8xDSckKz\r\nyvrhULniE8r6jwCeKsZY18NFdOAGIDdkDe40VZbRf01z3JzLLIMao5A7BZJo\r\nQ9FPpANarfCJfQH9fJUltsuBRkO4S4raaC1HyMCItXLNTO7iigVpdNam7jIO\r\n9ShcJoYw6yLtGwD9YZkzt+YrkI677WynTDqFSPK/sD0hafEU48nMedHoZOeY\r\n4dDGwwLNEwPi7SeifIpncN4M5RHSlGguwN7Vwrp21+9CMXiFbNNt+9jKwEqc\r\neynXW5u4JRz0paT0h+2m0a2BBTyKUm7D5JQ=\r\n=KBF2\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.0.2_1667125940493_0.46345166875216326"},"_hasShrinkwrap":false},"1.0.3":{"name":"l-fen","version":"1.0.3","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^14.14.35","@types/supertest":"^2.0.12","@types/yargs":"^16.0.0","@typescript-eslint/eslint-plugin":"^4.19.0","@typescript-eslint/parser":"^4.19.0","eslint":"^7.22.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^10.5.4","prettier":"^2.2.1","semantic-release":"^19.0.5","simple-git-hooks":"^2.2.0","typescript":"^4.2.3"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^7.1.6","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","winston":"^3.8.2","yargs":"^16.2.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"gitHead":"12d204d17d859fd6afc3ee31035c151e22a8caaa","_id":"l-fen@1.0.3","_nodeVersion":"16.17.1","_npmVersion":"8.19.2","dist":{"integrity":"sha512-mZGHg+jrvfiOfb3TaM/o+fxeed26ryLzVlenpTOJ2wcndEb6eBbKmlOtNpPqCwjsuh7mY7ejdTd/MTsAvA3x0A==","shasum":"d072e825fe4f1fab7ed4125641d81b24aa6cb257","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.0.3.tgz","fileCount":70,"unpackedSize":1181232,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIEc1nLZHCrAHls+D1GRZhuz7sKyd6hDBWZXfqE6nvsQlAiEAvHSvDAUvOj4G89IiLz9TNvXiaZ8J7FR3FBMKwkBiUN0="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjYQgEACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoLBQ//c5wheXod7aL32v8eZ4+NyJEc3pL5DxY/6Wnd0NfNlBZuNese\r\nBm+H5OFNBci/TloUdF8p7yvznCCJEoiRl5b3JyRSJlW9cdeZcFnp4DuXSDoi\r\njWeKAeeSe06p42w7b1J5lyHLrzLLTVuRgXtEoKShyn4JHs8oMAdWG6jQyUMr\r\nLZXzx4G/I+/tAqrFNbJbcsGYLD5MCinsaz9ftM7pI9PYI1SVBFNDMDB0HkIu\r\nLLMgqWNLFQ7VvFn0iS6Tk83JKSKRPBrdhyaT8Imbtd4lbZQM19usNzmmOPbG\r\nAYGLQq13nMOxgGDXsyiaravezcTQNxG+xY8HuRINm7CFKbCr5Jeb/2fHb1xG\r\nNhenE+bk1v4MucWgi3aA7macx2ntAr0pz0kuNEoX4Le4kYYGfo+NHhpVHZZ8\r\nZBevWP1H+GdLkjZPuxBTX34mz/OBaavOf56qsXP5sxDnTg1fMp70D/sBbCrP\r\nlCtt13bUFVMK1rdGet9c+iHvPuz5VayIlCWzxLHfRYlC9BI+qFSXVDxgETcU\r\nVkQwePDcA1qFaNV1zC3zHiyh8SJPbiLPSjL30HWYsr5NYQh8FKbyY1tiF54L\r\nsrx4PJx9S5MgTGDWA7SYnIanqPOOLoJs+zRmfCtJzoAqLW3nozPi2yqliyiy\r\naR+XF+J3/xGHUOe3UIW81ye4VR8LlXzBgfk=\r\n=7sF/\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.0.3_1667303428367_0.9255959578791746"},"_hasShrinkwrap":false},"1.0.4":{"name":"l-fen","version":"1.0.4","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^16.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^7.22.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^19.0.5","simple-git-hooks":"^2.2.0","typescript":"^4.2.3"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^8.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","winston":"^3.8.2","yargs":"^16.2.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"gitHead":"d7b11adf2479ef04aadaba11c11c51be4d3486c5","_id":"l-fen@1.0.4","_nodeVersion":"16.18.0","_npmVersion":"8.19.2","dist":{"integrity":"sha512-5mnId4zcJ2q3vtOyAoHz0SfHTXzINvf52LMYBBz8ER5g0ktH9ByPIVNuxKpaTZIyMLfus2pplN6lE0w8/DOhqQ==","shasum":"683a4be80d717ab1941019e5b0bfaa2a88e1ce7d","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.0.4.tgz","fileCount":71,"unpackedSize":1181573,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCcOW0h7GxFGvQLU2bujARFvj40NZkuP2SmELsFkejzJwIgA46JSk+aveff5m2Jra71HygsVk91qLdCHvOU6LBKNks="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjZ74dACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmqI+Q/+N2byyhURJRsvP7BlqlYt+V/xuFSFV7V5IxipHNdZQ+vBT3YD\r\n05HxAGff/1bt6hcbLRzHi0I+nFYdyA1mIlTb226PQ1Td/k0B6NEsrRvVjLxn\r\nMG7PLtH0u5X7GvIXp4OHNhH5k2133DutYCBaUFgBhP8q49fpExvt+45Fl44B\r\nh3sSSMSnQLCCw/tKCc3dSPA4VMRmCAhyyhfKDPHP5qUsd40g+mED56pIkkxR\r\n2YV+mnzu77kinruQKZjv6JaUMWOBK3BQnsmU+quncIUDXfJS3EkzYUYuwdB7\r\nxKC8JOt4XVccZaKGy1K0mHT5dOxyakyhUqsw4AsiE+n0jyqtkC0ZTyiZsscO\r\nJvPDMw6qMkT40p222AZN32mDEz/feWIZalmHnFQcBKkiiI/nsPcur28xsmc8\r\nZmjorquoeYRulyJiDJHmYTAXOZSHTc1gPgUhR2LI7/8QbUAI7FcWjp0hY1EB\r\nYw8ty53UWPqJILO/2w2h2uAkpdrkEUHGVsAi0gkohc7A2LOlg1y6D1rcP8Dd\r\nTsbLqIY8XRC9BG9S8naqLx6t+HUEHajGLMzLHNTxb0b5w/+ewoNABVysrEAJ\r\nyleeC8PqjxaBcrBTr9s0mQ49ri32ia55JXC1le8kLkv9WdVw2pJ7g+61RrdG\r\noNHc5262H/sFEvQtF1E0HE6iCUe8bN3q1KM=\r\n=gYQa\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.0.4_1667743261412_0.456755623825817"},"_hasShrinkwrap":false},"1.0.5":{"name":"l-fen","version":"1.0.5","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^7.22.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^19.0.5","simple-git-hooks":"^2.2.0","typescript":"^4.2.3"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^8.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"gitHead":"73d0b2553f4a0e2385da1a6300b88f5241795285","_id":"l-fen@1.0.5","_nodeVersion":"16.18.0","_npmVersion":"8.19.2","dist":{"integrity":"sha512-AFz1nW3kjvMLdPU0dJFqAKyEyUHZmoJsCdkf+94avWWwlqhI7ouu08fPzxENd3bwndV01NS123yW7t0tbiVHxA==","shasum":"7c5eae03f4e65c000b0e9d9578e2e22317e6dbbd","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.0.5.tgz","fileCount":71,"unpackedSize":1181813,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCFy2Uls08o4kA1qjZoFmjabray7HPIPI3ZU8C1pQ1knQIhAJfoVoIpRXAp+tl8mwMWRa8Bz7KixyHvXMaSDa1nYLh0"}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjZ76zACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2Vmo09A/9H8SlGhkIjh/qcs/dd7RTVA0kqFr0r6d4StmuWS5yWOfXpTFZ\r\nNjUO4PrIaGMfJ9cMfhvvGsnNLfMezoaj6EZe3cw7NwoFl5oo+tqMpjgLfGpK\r\nBKGN4zvFWz7YKVeALavFOIX5+Tf7leF8jja1MyU30RRwWoAY5S8A7yFWQ3JO\r\n8rZvPTURJbiavWxwkRePa3EOoA7HdDEdjPM/D1Y1Is9TCZNeq5qfbVqWiJBY\r\nl7k2Ol2A8EPdxc2f0nfULoN7mpuUt7k2oHTCS9ft3qA2EhVCzu+O4PoA4bB0\r\n9m0gOP8KpKDN4S8NLI0jU/0fJGJEuZNrwAkUaHWcmRw/0HmVX1ZUf0TzuaNz\r\n5GONP2PQyWaq/mCCda1eEvJf6iaS7p5a1gCX557F0fNNeoBbTgAAgtOScrob\r\nQZpfJQmjllPeMlgQU2ecXUdhaOby5vbRyc/z2pdKmHOs8Faepfsm+tXQrw51\r\niRxjHfkisfx5IzcjRZ9kX2sAFDYMLqLH2MU/ANdIFx/S3/8d864ojDsNHax5\r\nvEUomfm7M5dBadJD8T6ZYZ7cWAshEYu3OlMR7E69eaijGGsq6sLY8izlnI0j\r\n1poWxCfo5Qxm1RIQBLf6viKIAlLB/5Ogn0n/0TUkkbz56T5Ad2YeWQ0HWr6P\r\nv0ZX3Uxmd35f77roAgB/as75/wFoNKE6AFc=\r\n=nQXU\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.0.5_1667743411433_0.603979787717903"},"_hasShrinkwrap":false},"1.0.6":{"name":"l-fen","version":"1.0.6","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^20.0.0","simple-git-hooks":"^2.2.0","typescript":"^4.2.3"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"gitHead":"c0e19728e23e7669d172b67929ee1a201bc7a30e","_id":"l-fen@1.0.6","_nodeVersion":"18.14.2","_npmVersion":"8.19.2","dist":{"integrity":"sha512-8eXEqM3MFbxVjnyzQVV/RrhE7rEq4/vvLjME8xFqBV0NW1XA948/5GLxwif5crBPh6ANr3+cNtWyCaci/4d3JQ==","shasum":"032d96cddbc2823de6b1ef05cbff7d89636f4b53","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.0.6.tgz","fileCount":71,"unpackedSize":1182151,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIFkpVNkmOWwxVc77d/ySMr3rGdO3k8lGjz+JnBmOGhphAiA8ueumjNQrkDpXBVtOcYaeAW9sfntYQwqSTIWn+jq6lw=="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJkAajCACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpwQw/+JpuThLqL7g/npzweLMVaj7DXCaNhjJmIOUXH3ZFAgz4lngyn\r\nPy9uDwcf2As8duH2CGn/NlgmlnHmxAykXWf2cQqRZ0gJ+4suy0uDRRSfRqCO\r\n+QmhGz6yp2FXH0+wZBmGeT4OiQViQo8D34WJ0HSJ1MLL7ucDZGRe32y1rIU4\r\nxOikh7hIsVniXzR1646Cnz5DCAen1T57zR39lDgTGRNElq9mIu77gBgaIV84\r\nwo+O94niUof1WZQsB8Dc86GtPGJ0vmB9Rv9vndEjVZFlNu9pjUbQPc2qTTPm\r\nmzmoX0Us5tzqlZr2hMm4x0Y8pgqYynI02j65DI0RHnr51dAuCJlp8DX4T+cx\r\nTTAiVT/zNb6geNb3AXMcNzcGtOYftXnFBvAOpeo4yJ8hiTiXDGepLqMl9cBn\r\nx0oNZWTiiq6pMGv2p32TVZgpAxDkIX/qdhR878DEs4DHXvC8bjnjys2pcXpx\r\nWNWTroZZSikl90K4+e9uYD4tbBecKlQmPF/QyrFlmFQN9NU58CohzXOBUxlN\r\nNCmyUB7R3Y7+K6ugcqvUrZ7luZv43FXgyaIcqzqmThMMMQDjrdKNxcz9nypG\r\nRZB184s8g6Gy+mGGMeLA87kmqmAvJLgKLIMyEZeUmfYnkM35vrc4IWqg5hkl\r\nt1L+9Yzq5TSYAJ7V+3yPCBENXMyKp2LN7H8=\r\n=vvuE\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.0.6_1677830338660_0.2710673099946872"},"_hasShrinkwrap":false},"1.0.7":{"name":"l-fen","version":"1.0.7","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.2.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"gitHead":"c2943306f19e473dca1f4d975966043ac4789c3e","_id":"l-fen@1.0.7","_nodeVersion":"18.15.0","_npmVersion":"9.6.3","dist":{"integrity":"sha512-1udhozp0i8lVDKBGswtH4UgRez0feHJST+NUmc0zHTzaffFRIxeMHgTOWEFp94N/zuvQBQXgII5IqoRpji1I3Q==","shasum":"7f200eb4b6838891f6372363eb2c70444d7d26e2","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.0.7.tgz","fileCount":74,"unpackedSize":1377854,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQD5Mh0hkZcIizg4Lb0BzmE6eYiqFWAjYM7DLr6zofaHXAIhAP2RSp/H23RxYYOUeuVvggO8aFHc9VEcpqbWUlNZLi7g"}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJkLYCqACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoiAQ//dbGRAwTx9ne+VfUoec2pZXypIeNUA+TCfQ6H38R5Bf21w1gz\r\nm2A3XT3TZBPHcAmHnZq/LDH7cITdGii1z+jozb5Wjn/UhAtspR0Xl7tOmuTE\r\nwHSHYOXtz99vPGiFcGV89FEnE+BcmjMEvlqdzdWmxFKUBdyly8GPg4MqMV8F\r\nnsjli+wS6w1nGNEnJV+f5i0huz3K6ljkJGccA+z4np6EzfNMddxH7UHzzpJF\r\nNAWBCDTN77rwXqGn32Aummw6zvZxbQ6Fx0OM1ZB+3MabTqTSR/MWXPkb8GW8\r\nMWnbb6n6iXZB5izDgvkCwNoxJ7GeoNXJc7n2kktEh6JNcmzvIo8qDRqCFMOd\r\nV5InSvtRC7bat64SBJMMoWmdaVvy7CWhifHOlSIwhAWlvnlRZVJiRTvDccC+\r\nCktVv78Gf4w3novlH4JsTrRfS7IA0k1oug5nmX3DdjDMW4YDoaw9VQ7xcIAu\r\nOICXvsfEKDj3gSt+/He1GjFQc1RAT89rgwhUheolvM+iOVhucQM2yidAek+c\r\n/tlXVWW+GwHEqOncU/XnVVELYWKXhSQrR4Z1IAh/kUephksn2NrGFIu2mAxd\r\nTDh9CqwmJyc/LpgKB/OJOE+M0Ux3JF1Al75GbaOoQHpW7Xek2Zm94dO93HSs\r\nydPwpsHYiW34RGlrBlzNfSM0qQPdJviaE84=\r\n=Kb0T\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.0.7_1680703657908_0.29934057849100837"},"_hasShrinkwrap":false},"1.1.0-alpha.1":{"name":"l-fen","version":"1.1.0-alpha.1","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.2.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"types":"./lib/index.d.ts","readme":"## Description\n\n**l-fen** is a small runtime to execute a bundle of Serverless Functions like they were running in the cloud.\n\n:warning: only [GCP](https://cloud.google.com/) supported right now\n\n## Motivation\n\nWhen working on backend services and applications built by a multitude of individual Serverless Functions with various triggers, like HTTP, Pub/Sub or Storage/Bucket-Events, I always struggled with testing the whole setup locally.  \nEven if there's a 100% test coverage for each function itself, one function of the whole application might expect a different event than another function publishes.\n\nSo my approach was always to test the code itself with unit and e2e tests, deploy to a test or dev environment and test the whole process of the application again. If something fails, I had to look into the logs, hoping that I logged enough to narrow down the problem and ideally get the line where my code broke or wasn't working as expected.\n\nAfter a while, I decided to write something like an emulator for the various triggers and events.  \nThe goal was to locally run my backend service, which had several HTTP functions writing files to buckets, triggering other functions, publishing events via Pub/Sub with other functions subscribing to these.\n\nIn the end, I came up with **l-fen**, the _local function environment_.\n\n## Usage\n\nFirst, you have to build your Functions, since **l-fen** `requires()` the JS file.\n\nWhen that's done, you can start to create your `.fenrc.json` file.  \nThis file contains the whole configuration of your setup and the individual Functions.\nIt's required to to tell **l-fen** which Functions to load and when to call these.\n\nWhen everything is set up, just call `npx l-fen` to do it's magic.\n\n### Configuration\n\n```jsonc\n{\n  // the local folder containg all buckets and their files\n  \"storageDir\": \"./local-test-storage\",\n  // the port of the proxy, handling all HTTP requests and forwarding to the related HTTP Functions\n  \"httpProxyPort\": \"8080\",\n  // the port of the gRPC server handling stuff like Pub/Sub\n  \"grpcProxyPort\": \"50051\",\n  // the log level for l-fen. To print logs, created by Functions, set it to \"verbose\" or lower\n  \"loglevel\": \"verbose\",\n  // configuration for the Cloud Provider\n  \"provider\": {\n    // the Cloud Provider itself, right now, only GCP is supported\n    \"name\": \"GCP\",\n    // name of the project, used for some internal authentication processes\n    \"project\": \"my-test-project\"\n  },\n  // list of all Functions to be managed by l-fen\n  \"services\": [\n    {\n      // name of the Function, in case of HTTP Functions, it serves as the base route for the proxy\n      \"name\": \"http-handler\",\n      // path to the built main JS file\n      \"path\": \"./http-handler/index.js\",\n      // the exported entry point of the function\n      \"entryPoint\": \"handler\",\n      // the type of the function, \"HTTP\", \"PUBSUB\" or \"BUCKET\"\n      \"triggerType\": \"HTTP\",\n      // environment variables to be used by the Function\n      \"envVars\": {\n        \"PUBSUB_API_ENDPOINT\": \"localhost:50051\",\n        \"PROJECT_ID\": \"my-test-project\",\n        \"PUBSUB_TOPIC\": \"generic-topic\"\n      }\n    },\n    {\n      \"name\": \"file-writer\",\n      \"path\": \"./file-writer/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"PUBSUB\",\n      // if triggerType is either \"PUBSUB\" or \"BUCKET\", additional configuration has to be done here.\n      \"triggerConfig\": {\n        // the topic, the Pub/Sub function should subscribe\n        \"topic\": \"generic-topic\"\n      },\n      \"envVars\": {\n        \"MY_BUCKET\": \"my-bucket\"\n      }\n    },\n    {\n      \"name\": \"file-reader\",\n      \"path\": \"./file-reader/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"BUCKET\",\n      \"triggerConfig\": {\n        // file events the Function should listen on. Right now, only \"WRITE\" is supported\n        \"events\": [\"WRITE\"],\n        // the bucket the Function should listen on.\n        \"targetBucket\": \"my-bucket\"\n      },\n      \"envVars\": {}\n    }\n  ],\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n\n## Restrictions\n\n### Cloud Providers\n\nRight now, **l-fen** is only capable of handling Google Cloud Functions.  \nOther Cloud Providers are not yet supported.\n\n### Pub/Sub\n\nThe original idea was, to use **l-fen** out of the box, without the need of changing the Functions code to work with **l-fen**.\nUnfortunately, I didn't achieve that completely for Pub/Sub.\n\nTo allow **l-fen** to handle published event, the publisher has to use the gRPC server provided by **l-fen**.  \nWhen using the [Google Cloud Pub/Sub:Node.js Client](https://www.npmjs.com/package/@google-cloud/pubsub), the endpoint can be changed by instantiating the client with `new PubSub({apiEndpoint: 'localhost:50051'})`.\n\nI suggest to introduce an environment variable to the Function publishing events, to dynamically override the default endpoint and set that environment variable within the functions configuration in the `.fenrc.json`.\n\n## Under the hood\n\n**l-fen** consists of two main parts, the **orchestrator** and the **bootloader**, the brain and the heart of **l-fen**.\n\nThe orchestrator, the brain, plans the whole environment and knows what to do in certain cases and events.\nThe bootloader, the heart, is executing the Functions and providing all necessary data.\n\nThe orchestrator spawns new bootloader processes by executing the bootloader with required arguments.\n\n### The orchestrator\n\nThe orchestrator is responsible for loading the `.fenrc.json`, managing the Functions and setting up the local environment itself.\n\nDepending on the type of Functions, it adds a file watcher to the `storageDir` and calls the bootloader of the Functions that might be triggered by certain events in buckets.  \nThe same applies to HTTP and Pub/Sub Functions. The orchestrator provides a HTTP proxy and forwards any requests to the responsible Functions. It also starts the gRPC server that can be used by Pub/Sub to handle events and topics and call the according Functions.\n\n### The bootloader\n\nThe bootloader is responsible for starting a Function and providing all the required stuff the Function needs.\nIt's really nothing else like a small CLI tool, that expects certain arguments, like the payload it should forward to the Function or the environment variables the Function should be provided with.\n\nFor bucket and Pub/Sub Functions, it's quite straight forward. It `requires()` the Functions JS file, mapping the payload to the corresponding object and calling the `entryPoint` method with that object.\n\nIn case of an HTTP Function, the bootloader makes use of [express](https://www.npmjs.com/package/express) and [SuperTest](https://www.npmjs.com/package/supertest) create a proper request and handle stuff like different HTTP methods and headers.\n\nThe orchestrator is loading the `.fenrc.json`, managing all Functions, providing some basic setup like configuring a fake service account and file watcher on the specified `storageDir` and start the HTTP proxy.\n\nIt's also possible to start the bootloader without the orchestrator by running something like\n`npx l-fen-boot --event='{\\\"bucket\\\": \\\"user\\\", \\\"name\\\": \\\"111/b.json\\\"}' --path=\"../../../path/to/function/index.js\" --entryPoint=\"handleEvent\" --name=\"name-of-the-function\"`\n\n### Custom extension\n\nl-fen allows adding custom extensions that are executed for each loaded Function right before starting it.\n\nCreate your extension by exporting a class implementing the `LFenExtension` interface from `l-fen/types/extension.interface` and adding the absolute path to the config:\n\n```jsonc\n{\n  // all the other stuff, see above\n  ...\n  // here we add out extension\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n","readmeFilename":"README.md","gitHead":"bd7a8fcd1332568a2b5cbb1d57b5119e9a73ae6d","_id":"l-fen@1.1.0-alpha.1","_nodeVersion":"18.15.0","_npmVersion":"9.6.3","dist":{"integrity":"sha512-4V9b+qj1nQVi1LBbDZEl7nrGeWCpaPIKTINLyRZ0rMPag/+Ws/cq/0HPK5eMIAJ9g4A2JRSgtLfsOlmvGlMm7g==","shasum":"ce081845f518fa2559ea478594fc287d9b490151","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.1.0-alpha.1.tgz","fileCount":102,"unpackedSize":1390028,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIGEwn2mEhN5BQo7svEecB3aCUowh5z2w+ny9K1UKpoWvAiEAkKL3CZCon3LCcPTsblbu/d9C35eNZURlc0ubMqiOjKM="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJkNWtNACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmpDkg/+OD6q/Fh5Hl/Iw39yMngrSx7GwGEi1efOL6Wen0bx963a4+gU\r\n8+KXyyUh3fi91vSZx4wvXuB8BgfSRurd/r25OIttCRYkXy8YlNrEjLWt+uQY\r\noOSwHaD7gqMFLlOL9Fq1o113j0QR2XekZyenNON1vMvzGjdueXAOVXvnn/Co\r\nMapmf3gVGX9eKRArc/I4yTdu3kYlyAUixmakz8NM8DxDLrriGSxFWCI0pZ0I\r\nHTcTwdFJWOAQQJqjGjx7322WC58eGR5ITGGoB7XQaYsgvUpiSic2HnaF0PoK\r\n3mYvu1s4qLTlRmbIYh+2Sa03BJcZo0Tm3JjRlI2zQW3aKbXXFvTd55mT2c75\r\nIq0eR99AGMWuNlD/ND38XDBGcpoPpYC/SC5F3vxifI6ii3y0lGVDFuJGzT7f\r\nmK5jHNjNnjR8weSh0VB/JjBrEtYHICs5NAwobWQ6EbQv2MK8ByEATZdzqjwg\r\ndobWD0q3L/xD71j6DAM3DPXuwj6RqOmR7lGtkRjq5w1rVbwLzj5aPz+zb056\r\n0FDEre2BSmd5p1/GZgNhGw9GNYN97lplLmowwo/5il9DvtMlWMmOnyh/pESU\r\nwj3ej0IUgzNMUA36P2xThmcN0XeYdin0wmWL8FlIUuMNYNXAQmgeUGS+E3j3\r\nsHi2ByNSLKCg09Vp6536nXXwxvgz6/AZX3o=\r\n=X3Qt\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.1.0-alpha.1_1681222477182_0.5236232333963622"},"_hasShrinkwrap":false},"1.1.0-alpha.2":{"name":"l-fen","version":"1.1.0-alpha.2","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.2.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"types":"./lib/index.d.ts","readme":"## Description\n\n**l-fen** is a small runtime to execute a bundle of Serverless Functions like they were running in the cloud.\n\n:warning: only [GCP](https://cloud.google.com/) supported right now\n\n## Motivation\n\nWhen working on backend services and applications built by a multitude of individual Serverless Functions with various triggers, like HTTP, Pub/Sub or Storage/Bucket-Events, I always struggled with testing the whole setup locally.  \nEven if there's a 100% test coverage for each function itself, one function of the whole application might expect a different event than another function publishes.\n\nSo my approach was always to test the code itself with unit and e2e tests, deploy to a test or dev environment and test the whole process of the application again. If something fails, I had to look into the logs, hoping that I logged enough to narrow down the problem and ideally get the line where my code broke or wasn't working as expected.\n\nAfter a while, I decided to write something like an emulator for the various triggers and events.  \nThe goal was to locally run my backend service, which had several HTTP functions writing files to buckets, triggering other functions, publishing events via Pub/Sub with other functions subscribing to these.\n\nIn the end, I came up with **l-fen**, the _local function environment_.\n\n## Usage\n\nFirst, you have to build your Functions, since **l-fen** `requires()` the JS file.\n\nWhen that's done, you can start to create your `.fenrc.json` file.  \nThis file contains the whole configuration of your setup and the individual Functions.\nIt's required to to tell **l-fen** which Functions to load and when to call these.\n\nWhen everything is set up, just call `npx l-fen` to do it's magic.\n\n### Configuration\n\n```jsonc\n{\n  // the local folder containg all buckets and their files\n  \"storageDir\": \"./local-test-storage\",\n  // the port of the proxy, handling all HTTP requests and forwarding to the related HTTP Functions\n  \"httpProxyPort\": \"8080\",\n  // the port of the gRPC server handling stuff like Pub/Sub\n  \"grpcProxyPort\": \"50051\",\n  // the log level for l-fen. To print logs, created by Functions, set it to \"verbose\" or lower\n  \"loglevel\": \"verbose\",\n  // configuration for the Cloud Provider\n  \"provider\": {\n    // the Cloud Provider itself, right now, only GCP is supported\n    \"name\": \"GCP\",\n    // name of the project, used for some internal authentication processes\n    \"project\": \"my-test-project\"\n  },\n  // list of all Functions to be managed by l-fen\n  \"services\": [\n    {\n      // name of the Function, in case of HTTP Functions, it serves as the base route for the proxy\n      \"name\": \"http-handler\",\n      // path to the built main JS file\n      \"path\": \"./http-handler/index.js\",\n      // the exported entry point of the function\n      \"entryPoint\": \"handler\",\n      // the type of the function, \"HTTP\", \"PUBSUB\" or \"BUCKET\"\n      \"triggerType\": \"HTTP\",\n      // environment variables to be used by the Function\n      \"envVars\": {\n        \"PUBSUB_API_ENDPOINT\": \"localhost:50051\",\n        \"PROJECT_ID\": \"my-test-project\",\n        \"PUBSUB_TOPIC\": \"generic-topic\"\n      }\n    },\n    {\n      \"name\": \"file-writer\",\n      \"path\": \"./file-writer/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"PUBSUB\",\n      // if triggerType is either \"PUBSUB\" or \"BUCKET\", additional configuration has to be done here.\n      \"triggerConfig\": {\n        // the topic, the Pub/Sub function should subscribe\n        \"topic\": \"generic-topic\"\n      },\n      \"envVars\": {\n        \"MY_BUCKET\": \"my-bucket\"\n      }\n    },\n    {\n      \"name\": \"file-reader\",\n      \"path\": \"./file-reader/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"BUCKET\",\n      \"triggerConfig\": {\n        // file events the Function should listen on. Right now, only \"WRITE\" is supported\n        \"events\": [\"WRITE\"],\n        // the bucket the Function should listen on.\n        \"targetBucket\": \"my-bucket\"\n      },\n      \"envVars\": {}\n    }\n  ],\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n\n## Restrictions\n\n### Cloud Providers\n\nRight now, **l-fen** is only capable of handling Google Cloud Functions.  \nOther Cloud Providers are not yet supported.\n\n### Pub/Sub\n\nThe original idea was, to use **l-fen** out of the box, without the need of changing the Functions code to work with **l-fen**.\nUnfortunately, I didn't achieve that completely for Pub/Sub.\n\nTo allow **l-fen** to handle published event, the publisher has to use the gRPC server provided by **l-fen**.  \nWhen using the [Google Cloud Pub/Sub:Node.js Client](https://www.npmjs.com/package/@google-cloud/pubsub), the endpoint can be changed by instantiating the client with `new PubSub({apiEndpoint: 'localhost:50051'})`.\n\nI suggest to introduce an environment variable to the Function publishing events, to dynamically override the default endpoint and set that environment variable within the functions configuration in the `.fenrc.json`.\n\n## Under the hood\n\n**l-fen** consists of two main parts, the **orchestrator** and the **bootloader**, the brain and the heart of **l-fen**.\n\nThe orchestrator, the brain, plans the whole environment and knows what to do in certain cases and events.\nThe bootloader, the heart, is executing the Functions and providing all necessary data.\n\nThe orchestrator spawns new bootloader processes by executing the bootloader with required arguments.\n\n### The orchestrator\n\nThe orchestrator is responsible for loading the `.fenrc.json`, managing the Functions and setting up the local environment itself.\n\nDepending on the type of Functions, it adds a file watcher to the `storageDir` and calls the bootloader of the Functions that might be triggered by certain events in buckets.  \nThe same applies to HTTP and Pub/Sub Functions. The orchestrator provides a HTTP proxy and forwards any requests to the responsible Functions. It also starts the gRPC server that can be used by Pub/Sub to handle events and topics and call the according Functions.\n\n### The bootloader\n\nThe bootloader is responsible for starting a Function and providing all the required stuff the Function needs.\nIt's really nothing else like a small CLI tool, that expects certain arguments, like the payload it should forward to the Function or the environment variables the Function should be provided with.\n\nFor bucket and Pub/Sub Functions, it's quite straight forward. It `requires()` the Functions JS file, mapping the payload to the corresponding object and calling the `entryPoint` method with that object.\n\nIn case of an HTTP Function, the bootloader makes use of [express](https://www.npmjs.com/package/express) and [SuperTest](https://www.npmjs.com/package/supertest) create a proper request and handle stuff like different HTTP methods and headers.\n\nThe orchestrator is loading the `.fenrc.json`, managing all Functions, providing some basic setup like configuring a fake service account and file watcher on the specified `storageDir` and start the HTTP proxy.\n\nIt's also possible to start the bootloader without the orchestrator by running something like\n`npx l-fen-boot --event='{\\\"bucket\\\": \\\"user\\\", \\\"name\\\": \\\"111/b.json\\\"}' --path=\"../../../path/to/function/index.js\" --entryPoint=\"handleEvent\" --name=\"name-of-the-function\"`\n\n### Custom extension\n\nl-fen allows adding custom extensions that are executed for each loaded Function right before starting it.\n\nCreate your extension by exporting a class implementing the `LFenExtension` interface from `l-fen/types/extension.interface` and adding the absolute path to the config:\n\n```jsonc\n{\n  // all the other stuff, see above\n  ...\n  // here we add out extension\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n","readmeFilename":"README.md","gitHead":"38a14cbf74385d085c1def2d61e09560cbe8be98","_id":"l-fen@1.1.0-alpha.2","_nodeVersion":"18.19.0","_npmVersion":"9.6.3","dist":{"integrity":"sha512-/ITdlOxqfjdvFSbfH1J2WKycT1ZMMMrDxR4NZafoYzynk4fJSle4P1qROjM2rG2NVTcsiyw2/psc9ghaX7v7fQ==","shasum":"ddd5c761dd6c87968b76e5b52d0b88cd8cc955bf","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.1.0-alpha.2.tgz","fileCount":102,"unpackedSize":1390349,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIAkbM3dYiqVr++b97zhbki8jPLQUAUz7pu1GAXioaKWiAiBXT+ZU0ErxLc5O7DVIoz9o7kFcXx2fDoEpzSM50XAjEw=="}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.1.0-alpha.2_1705567548957_0.5432509545499671"},"_hasShrinkwrap":false},"1.1.0-alpha.3":{"name":"l-fen","version":"1.1.0-alpha.3","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.2.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"types":"./lib/index.d.ts","readme":"## Description\n\n**l-fen** is a small runtime to execute a bundle of Serverless Functions like they were running in the cloud.\n\n:warning: only [GCP](https://cloud.google.com/) supported right now\n\n## Motivation\n\nWhen working on backend services and applications built by a multitude of individual Serverless Functions with various triggers, like HTTP, Pub/Sub or Storage/Bucket-Events, I always struggled with testing the whole setup locally.  \nEven if there's a 100% test coverage for each function itself, one function of the whole application might expect a different event than another function publishes.\n\nSo my approach was always to test the code itself with unit and e2e tests, deploy to a test or dev environment and test the whole process of the application again. If something fails, I had to look into the logs, hoping that I logged enough to narrow down the problem and ideally get the line where my code broke or wasn't working as expected.\n\nAfter a while, I decided to write something like an emulator for the various triggers and events.  \nThe goal was to locally run my backend service, which had several HTTP functions writing files to buckets, triggering other functions, publishing events via Pub/Sub with other functions subscribing to these.\n\nIn the end, I came up with **l-fen**, the _local function environment_.\n\n## Usage\n\nFirst, you have to build your Functions, since **l-fen** `requires()` the JS file.\n\nWhen that's done, you can start to create your `.fenrc.json` file.  \nThis file contains the whole configuration of your setup and the individual Functions.\nIt's required to to tell **l-fen** which Functions to load and when to call these.\n\nWhen everything is set up, just call `npx l-fen` to do it's magic.\n\n### Configuration\n\n```jsonc\n{\n  // the local folder containg all buckets and their files\n  \"storageDir\": \"./local-test-storage\",\n  // the port of the proxy, handling all HTTP requests and forwarding to the related HTTP Functions\n  \"httpProxyPort\": \"8080\",\n  // the port of the gRPC server handling stuff like Pub/Sub\n  \"grpcProxyPort\": \"50051\",\n  // the log level for l-fen. To print logs, created by Functions, set it to \"verbose\" or lower\n  \"loglevel\": \"verbose\",\n  // configuration for the Cloud Provider\n  \"provider\": {\n    // the Cloud Provider itself, right now, only GCP is supported\n    \"name\": \"GCP\",\n    // name of the project, used for some internal authentication processes\n    \"project\": \"my-test-project\"\n  },\n  // list of all Functions to be managed by l-fen\n  \"services\": [\n    {\n      // name of the Function, in case of HTTP Functions, it serves as the base route for the proxy\n      \"name\": \"http-handler\",\n      // path to the built main JS file\n      \"path\": \"./http-handler/index.js\",\n      // the exported entry point of the function\n      \"entryPoint\": \"handler\",\n      // the type of the function, \"HTTP\", \"PUBSUB\" or \"BUCKET\"\n      \"triggerType\": \"HTTP\",\n      // environment variables to be used by the Function\n      \"envVars\": {\n        \"PUBSUB_API_ENDPOINT\": \"localhost:50051\",\n        \"PROJECT_ID\": \"my-test-project\",\n        \"PUBSUB_TOPIC\": \"generic-topic\"\n      }\n    },\n    {\n      \"name\": \"file-writer\",\n      \"path\": \"./file-writer/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"PUBSUB\",\n      // if triggerType is either \"PUBSUB\" or \"BUCKET\", additional configuration has to be done here.\n      \"triggerConfig\": {\n        // the topic, the Pub/Sub function should subscribe\n        \"topic\": \"generic-topic\"\n      },\n      \"envVars\": {\n        \"MY_BUCKET\": \"my-bucket\"\n      }\n    },\n    {\n      \"name\": \"file-reader\",\n      \"path\": \"./file-reader/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"BUCKET\",\n      \"triggerConfig\": {\n        // file events the Function should listen on. Right now, only \"WRITE\" is supported\n        \"events\": [\"WRITE\"],\n        // the bucket the Function should listen on.\n        \"targetBucket\": \"my-bucket\"\n      },\n      \"envVars\": {}\n    }\n  ],\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n\n## Restrictions\n\n### Cloud Providers\n\nRight now, **l-fen** is only capable of handling Google Cloud Functions.  \nOther Cloud Providers are not yet supported.\n\n### Pub/Sub\n\nThe original idea was, to use **l-fen** out of the box, without the need of changing the Functions code to work with **l-fen**.\nUnfortunately, I didn't achieve that completely for Pub/Sub.\n\nTo allow **l-fen** to handle published event, the publisher has to use the gRPC server provided by **l-fen**.  \nWhen using the [Google Cloud Pub/Sub:Node.js Client](https://www.npmjs.com/package/@google-cloud/pubsub), the endpoint can be changed by instantiating the client with `new PubSub({apiEndpoint: 'localhost:50051'})`.\n\nI suggest to introduce an environment variable to the Function publishing events, to dynamically override the default endpoint and set that environment variable within the functions configuration in the `.fenrc.json`.\n\n## Under the hood\n\n**l-fen** consists of two main parts, the **orchestrator** and the **bootloader**, the brain and the heart of **l-fen**.\n\nThe orchestrator, the brain, plans the whole environment and knows what to do in certain cases and events.\nThe bootloader, the heart, is executing the Functions and providing all necessary data.\n\nThe orchestrator spawns new bootloader processes by executing the bootloader with required arguments.\n\n### The orchestrator\n\nThe orchestrator is responsible for loading the `.fenrc.json`, managing the Functions and setting up the local environment itself.\n\nDepending on the type of Functions, it adds a file watcher to the `storageDir` and calls the bootloader of the Functions that might be triggered by certain events in buckets.  \nThe same applies to HTTP and Pub/Sub Functions. The orchestrator provides a HTTP proxy and forwards any requests to the responsible Functions. It also starts the gRPC server that can be used by Pub/Sub to handle events and topics and call the according Functions.\n\n### The bootloader\n\nThe bootloader is responsible for starting a Function and providing all the required stuff the Function needs.\nIt's really nothing else like a small CLI tool, that expects certain arguments, like the payload it should forward to the Function or the environment variables the Function should be provided with.\n\nFor bucket and Pub/Sub Functions, it's quite straight forward. It `requires()` the Functions JS file, mapping the payload to the corresponding object and calling the `entryPoint` method with that object.\n\nIn case of an HTTP Function, the bootloader makes use of [express](https://www.npmjs.com/package/express) and [SuperTest](https://www.npmjs.com/package/supertest) create a proper request and handle stuff like different HTTP methods and headers.\n\nThe orchestrator is loading the `.fenrc.json`, managing all Functions, providing some basic setup like configuring a fake service account and file watcher on the specified `storageDir` and start the HTTP proxy.\n\nIt's also possible to start the bootloader without the orchestrator by running something like\n`npx l-fen-boot --event='{\\\"bucket\\\": \\\"user\\\", \\\"name\\\": \\\"111/b.json\\\"}' --path=\"../../../path/to/function/index.js\" --entryPoint=\"handleEvent\" --name=\"name-of-the-function\"`\n\n### Custom extension\n\nl-fen allows adding custom extensions that are executed for each loaded Function right before starting it.\n\nCreate your extension by exporting a class implementing the `LFenExtension` interface from `l-fen/types/extension.interface` and adding the absolute path to the config:\n\n```jsonc\n{\n  // all the other stuff, see above\n  ...\n  // here we add out extension\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n","readmeFilename":"README.md","gitHead":"951d1ae70700d528d22a297ca2a1d23cbb0c0ffc","_id":"l-fen@1.1.0-alpha.3","_nodeVersion":"18.19.0","_npmVersion":"9.6.3","dist":{"integrity":"sha512-5gz+I6hZC9RVcMQa95BnfApqjDrhYy5pBPGvOFkQFySC+oCt+v9fCxWwJBaA8pKSarQJazEpmF18m6OI0pEAFw==","shasum":"4f097f89c9f1f4a9625b44c82a9f2557b966b344","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.1.0-alpha.3.tgz","fileCount":102,"unpackedSize":1391590,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCgsXP039L33NQei16gBh7cjvXnAjHa68WwWm7hwMKzhgIgfLVGawuozC4Rwp5tZvvLGTNwHLUudw+Bu+LxBTx3aBs="}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.1.0-alpha.3_1705910565259_0.07926600800587846"},"_hasShrinkwrap":false},"1.1.0-alpha.4":{"name":"l-fen","version":"1.1.0-alpha.4","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.2.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"types":"./lib/index.d.ts","readme":"## Description\n\n**l-fen** is a small runtime to execute a bundle of Serverless Functions like they were running in the cloud.\n\n:warning: only [GCP](https://cloud.google.com/) supported right now\n\n## Motivation\n\nWhen working on backend services and applications built by a multitude of individual Serverless Functions with various triggers, like HTTP, Pub/Sub or Storage/Bucket-Events, I always struggled with testing the whole setup locally.  \nEven if there's a 100% test coverage for each function itself, one function of the whole application might expect a different event than another function publishes.\n\nSo my approach was always to test the code itself with unit and e2e tests, deploy to a test or dev environment and test the whole process of the application again. If something fails, I had to look into the logs, hoping that I logged enough to narrow down the problem and ideally get the line where my code broke or wasn't working as expected.\n\nAfter a while, I decided to write something like an emulator for the various triggers and events.  \nThe goal was to locally run my backend service, which had several HTTP functions writing files to buckets, triggering other functions, publishing events via Pub/Sub with other functions subscribing to these.\n\nIn the end, I came up with **l-fen**, the _local function environment_.\n\n## Usage\n\nFirst, you have to build your Functions, since **l-fen** `requires()` the JS file.\n\nWhen that's done, you can start to create your `.fenrc.json` file.  \nThis file contains the whole configuration of your setup and the individual Functions.\nIt's required to to tell **l-fen** which Functions to load and when to call these.\n\nWhen everything is set up, just call `npx l-fen` to do it's magic.\n\n### Configuration\n\n```jsonc\n{\n  // the local folder containg all buckets and their files\n  \"storageDir\": \"./local-test-storage\",\n  // the port of the proxy, handling all HTTP requests and forwarding to the related HTTP Functions\n  \"httpProxyPort\": \"8080\",\n  // the port of the gRPC server handling stuff like Pub/Sub\n  \"grpcProxyPort\": \"50051\",\n  // the log level for l-fen. To print logs, created by Functions, set it to \"verbose\" or lower\n  \"loglevel\": \"verbose\",\n  // configuration for the Cloud Provider\n  \"provider\": {\n    // the Cloud Provider itself, right now, only GCP is supported\n    \"name\": \"GCP\",\n    // name of the project, used for some internal authentication processes\n    \"project\": \"my-test-project\"\n  },\n  // list of all Functions to be managed by l-fen\n  \"services\": [\n    {\n      // name of the Function, in case of HTTP Functions, it serves as the base route for the proxy\n      \"name\": \"http-handler\",\n      // path to the built main JS file\n      \"path\": \"./http-handler/index.js\",\n      // the exported entry point of the function\n      \"entryPoint\": \"handler\",\n      // the type of the function, \"HTTP\", \"PUBSUB\" or \"BUCKET\"\n      \"triggerType\": \"HTTP\",\n      // environment variables to be used by the Function\n      \"envVars\": {\n        \"PUBSUB_API_ENDPOINT\": \"localhost:50051\",\n        \"PROJECT_ID\": \"my-test-project\",\n        \"PUBSUB_TOPIC\": \"generic-topic\"\n      }\n    },\n    {\n      \"name\": \"file-writer\",\n      \"path\": \"./file-writer/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"PUBSUB\",\n      // if triggerType is either \"PUBSUB\" or \"BUCKET\", additional configuration has to be done here.\n      \"triggerConfig\": {\n        // the topic, the Pub/Sub function should subscribe\n        \"topic\": \"generic-topic\"\n      },\n      \"envVars\": {\n        \"MY_BUCKET\": \"my-bucket\"\n      }\n    },\n    {\n      \"name\": \"file-reader\",\n      \"path\": \"./file-reader/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"BUCKET\",\n      \"triggerConfig\": {\n        // file events the Function should listen on. Right now, only \"WRITE\" is supported\n        \"events\": [\"WRITE\"],\n        // the bucket the Function should listen on.\n        \"targetBucket\": \"my-bucket\"\n      },\n      \"envVars\": {}\n    }\n  ],\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n\n## Restrictions\n\n### Cloud Providers\n\nRight now, **l-fen** is only capable of handling Google Cloud Functions.  \nOther Cloud Providers are not yet supported.\n\n### Pub/Sub\n\nThe original idea was, to use **l-fen** out of the box, without the need of changing the Functions code to work with **l-fen**.\nUnfortunately, I didn't achieve that completely for Pub/Sub.\n\nTo allow **l-fen** to handle published event, the publisher has to use the gRPC server provided by **l-fen**.  \nWhen using the [Google Cloud Pub/Sub:Node.js Client](https://www.npmjs.com/package/@google-cloud/pubsub), the endpoint can be changed by instantiating the client with `new PubSub({apiEndpoint: 'localhost:50051'})`.\n\nI suggest to introduce an environment variable to the Function publishing events, to dynamically override the default endpoint and set that environment variable within the functions configuration in the `.fenrc.json`.\n\n## Under the hood\n\n**l-fen** consists of two main parts, the **orchestrator** and the **bootloader**, the brain and the heart of **l-fen**.\n\nThe orchestrator, the brain, plans the whole environment and knows what to do in certain cases and events.\nThe bootloader, the heart, is executing the Functions and providing all necessary data.\n\nThe orchestrator spawns new bootloader processes by executing the bootloader with required arguments.\n\n### The orchestrator\n\nThe orchestrator is responsible for loading the `.fenrc.json`, managing the Functions and setting up the local environment itself.\n\nDepending on the type of Functions, it adds a file watcher to the `storageDir` and calls the bootloader of the Functions that might be triggered by certain events in buckets.  \nThe same applies to HTTP and Pub/Sub Functions. The orchestrator provides a HTTP proxy and forwards any requests to the responsible Functions. It also starts the gRPC server that can be used by Pub/Sub to handle events and topics and call the according Functions.\n\n### The bootloader\n\nThe bootloader is responsible for starting a Function and providing all the required stuff the Function needs.\nIt's really nothing else like a small CLI tool, that expects certain arguments, like the payload it should forward to the Function or the environment variables the Function should be provided with.\n\nFor bucket and Pub/Sub Functions, it's quite straight forward. It `requires()` the Functions JS file, mapping the payload to the corresponding object and calling the `entryPoint` method with that object.\n\nIn case of an HTTP Function, the bootloader makes use of [express](https://www.npmjs.com/package/express) and [SuperTest](https://www.npmjs.com/package/supertest) create a proper request and handle stuff like different HTTP methods and headers.\n\nThe orchestrator is loading the `.fenrc.json`, managing all Functions, providing some basic setup like configuring a fake service account and file watcher on the specified `storageDir` and start the HTTP proxy.\n\nIt's also possible to start the bootloader without the orchestrator by running something like\n`npx l-fen-boot --event='{\\\"bucket\\\": \\\"user\\\", \\\"name\\\": \\\"111/b.json\\\"}' --path=\"../../../path/to/function/index.js\" --entryPoint=\"handleEvent\" --name=\"name-of-the-function\"`\n\n### Custom extension\n\nl-fen allows adding custom extensions that are executed for each loaded Function right before starting it.\n\nCreate your extension by exporting a class implementing the `LFenExtension` interface from `l-fen/types/extension.interface` and adding the absolute path to the config:\n\n```jsonc\n{\n  // all the other stuff, see above\n  ...\n  // here we add out extension\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n","readmeFilename":"README.md","gitHead":"699028f4c46c09e9c955c6e082fc17f7eed58c2f","_id":"l-fen@1.1.0-alpha.4","_nodeVersion":"18.19.0","_npmVersion":"9.6.3","dist":{"integrity":"sha512-zfj/GYDhZ3dOzwyGW9rcB7qUaKXzURRaIl7L/mVcDSHmI3h7wWWblAldp/lMuO9hdu3PyjgBXXxy2nhRGdb4Nw==","shasum":"de6ac6622c621af09112b65f41ae56e6e4bb8188","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.1.0-alpha.4.tgz","fileCount":102,"unpackedSize":1392100,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDWiMnWS/gv10d8bzz5wM0vJylwrYeBuFadbuCCgRUA0wIgVYIqBNlb2jw70GNVDvBJBHr1KboK0OCJakkuuhgu+g4="}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.1.0-alpha.4_1705912369235_0.4293390931186283"},"_hasShrinkwrap":false},"1.1.0-alpha.5":{"name":"l-fen","version":"1.1.0-alpha.5","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.2.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"types":"./lib/index.d.ts","readme":"## Description\n\n**l-fen** is a small runtime to execute a bundle of Serverless Functions like they were running in the cloud.\n\n:warning: only [GCP](https://cloud.google.com/) supported right now\n\n## Motivation\n\nWhen working on backend services and applications built by a multitude of individual Serverless Functions with various triggers, like HTTP, Pub/Sub or Storage/Bucket-Events, I always struggled with testing the whole setup locally.  \nEven if there's a 100% test coverage for each function itself, one function of the whole application might expect a different event than another function publishes.\n\nSo my approach was always to test the code itself with unit and e2e tests, deploy to a test or dev environment and test the whole process of the application again. If something fails, I had to look into the logs, hoping that I logged enough to narrow down the problem and ideally get the line where my code broke or wasn't working as expected.\n\nAfter a while, I decided to write something like an emulator for the various triggers and events.  \nThe goal was to locally run my backend service, which had several HTTP functions writing files to buckets, triggering other functions, publishing events via Pub/Sub with other functions subscribing to these.\n\nIn the end, I came up with **l-fen**, the _local function environment_.\n\n## Usage\n\nFirst, you have to build your Functions, since **l-fen** `requires()` the JS file.\n\nWhen that's done, you can start to create your `.fenrc.json` file.  \nThis file contains the whole configuration of your setup and the individual Functions.\nIt's required to to tell **l-fen** which Functions to load and when to call these.\n\nWhen everything is set up, just call `npx l-fen` to do it's magic.\n\n### Configuration\n\n```jsonc\n{\n  // the local folder containg all buckets and their files\n  \"storageDir\": \"./local-test-storage\",\n  // the port of the proxy, handling all HTTP requests and forwarding to the related HTTP Functions\n  \"httpProxyPort\": \"8080\",\n  // the port of the gRPC server handling stuff like Pub/Sub\n  \"grpcProxyPort\": \"50051\",\n  // the log level for l-fen. To print logs, created by Functions, set it to \"verbose\" or lower\n  \"loglevel\": \"verbose\",\n  // configuration for the Cloud Provider\n  \"provider\": {\n    // the Cloud Provider itself, right now, only GCP is supported\n    \"name\": \"GCP\",\n    // name of the project, used for some internal authentication processes\n    \"project\": \"my-test-project\"\n  },\n  // list of all Functions to be managed by l-fen\n  \"services\": [\n    {\n      // name of the Function, in case of HTTP Functions, it serves as the base route for the proxy\n      \"name\": \"http-handler\",\n      // path to the built main JS file\n      \"path\": \"./http-handler/index.js\",\n      // the exported entry point of the function\n      \"entryPoint\": \"handler\",\n      // the type of the function, \"HTTP\", \"PUBSUB\" or \"BUCKET\"\n      \"triggerType\": \"HTTP\",\n      // environment variables to be used by the Function\n      \"envVars\": {\n        \"PUBSUB_API_ENDPOINT\": \"localhost:50051\",\n        \"PROJECT_ID\": \"my-test-project\",\n        \"PUBSUB_TOPIC\": \"generic-topic\"\n      }\n    },\n    {\n      \"name\": \"file-writer\",\n      \"path\": \"./file-writer/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"PUBSUB\",\n      // if triggerType is either \"PUBSUB\" or \"BUCKET\", additional configuration has to be done here.\n      \"triggerConfig\": {\n        // the topic, the Pub/Sub function should subscribe\n        \"topic\": \"generic-topic\"\n      },\n      \"envVars\": {\n        \"MY_BUCKET\": \"my-bucket\"\n      }\n    },\n    {\n      \"name\": \"file-reader\",\n      \"path\": \"./file-reader/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"BUCKET\",\n      \"triggerConfig\": {\n        // file events the Function should listen on. Right now, only \"WRITE\" is supported\n        \"events\": [\"WRITE\"],\n        // the bucket the Function should listen on.\n        \"targetBucket\": \"my-bucket\"\n      },\n      \"envVars\": {}\n    }\n  ],\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n\n## Restrictions\n\n### Cloud Providers\n\nRight now, **l-fen** is only capable of handling Google Cloud Functions.  \nOther Cloud Providers are not yet supported.\n\n### Pub/Sub\n\nThe original idea was, to use **l-fen** out of the box, without the need of changing the Functions code to work with **l-fen**.\nUnfortunately, I didn't achieve that completely for Pub/Sub.\n\nTo allow **l-fen** to handle published event, the publisher has to use the gRPC server provided by **l-fen**.  \nWhen using the [Google Cloud Pub/Sub:Node.js Client](https://www.npmjs.com/package/@google-cloud/pubsub), the endpoint can be changed by instantiating the client with `new PubSub({apiEndpoint: 'localhost:50051'})`.\n\nI suggest to introduce an environment variable to the Function publishing events, to dynamically override the default endpoint and set that environment variable within the functions configuration in the `.fenrc.json`.\n\n## Under the hood\n\n**l-fen** consists of two main parts, the **orchestrator** and the **bootloader**, the brain and the heart of **l-fen**.\n\nThe orchestrator, the brain, plans the whole environment and knows what to do in certain cases and events.\nThe bootloader, the heart, is executing the Functions and providing all necessary data.\n\nThe orchestrator spawns new bootloader processes by executing the bootloader with required arguments.\n\n### The orchestrator\n\nThe orchestrator is responsible for loading the `.fenrc.json`, managing the Functions and setting up the local environment itself.\n\nDepending on the type of Functions, it adds a file watcher to the `storageDir` and calls the bootloader of the Functions that might be triggered by certain events in buckets.  \nThe same applies to HTTP and Pub/Sub Functions. The orchestrator provides a HTTP proxy and forwards any requests to the responsible Functions. It also starts the gRPC server that can be used by Pub/Sub to handle events and topics and call the according Functions.\n\n### The bootloader\n\nThe bootloader is responsible for starting a Function and providing all the required stuff the Function needs.\nIt's really nothing else like a small CLI tool, that expects certain arguments, like the payload it should forward to the Function or the environment variables the Function should be provided with.\n\nFor bucket and Pub/Sub Functions, it's quite straight forward. It `requires()` the Functions JS file, mapping the payload to the corresponding object and calling the `entryPoint` method with that object.\n\nIn case of an HTTP Function, the bootloader makes use of [express](https://www.npmjs.com/package/express) and [SuperTest](https://www.npmjs.com/package/supertest) create a proper request and handle stuff like different HTTP methods and headers.\n\nThe orchestrator is loading the `.fenrc.json`, managing all Functions, providing some basic setup like configuring a fake service account and file watcher on the specified `storageDir` and start the HTTP proxy.\n\nIt's also possible to start the bootloader without the orchestrator by running something like\n`npx l-fen-boot --event='{\\\"bucket\\\": \\\"user\\\", \\\"name\\\": \\\"111/b.json\\\"}' --path=\"../../../path/to/function/index.js\" --entryPoint=\"handleEvent\" --name=\"name-of-the-function\"`\n\n### Custom extension\n\nl-fen allows adding custom extensions that are executed for each loaded Function right before starting it.\n\nCreate your extension by exporting a class implementing the `LFenExtension` interface from `l-fen/types/extension.interface` and adding the absolute path to the config:\n\n```jsonc\n{\n  // all the other stuff, see above\n  ...\n  // here we add out extension\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n","readmeFilename":"README.md","gitHead":"92f502b15304a0fceefa066f09295f3e8c5fd089","_id":"l-fen@1.1.0-alpha.5","_nodeVersion":"18.19.0","_npmVersion":"9.6.3","dist":{"integrity":"sha512-GbgXb51TVydyFDMBsO9DvIOxOfsymsz77j3F63gGFUcak1ZoYDXqys3j/9nVHlhK8YuTjQsCL99FCD7BOCN8Bg==","shasum":"6c5adf32968698dbb25720885ce990a55e0415fc","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.1.0-alpha.5.tgz","fileCount":102,"unpackedSize":1392301,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDE89zecQdX9iE1CxsyH4f0fjJR/BntwVq+eV8kNHsb4QIhALjkzOgDd3EWpC8ZdfVkYKzDXMBcWhdhKiDZuvRt9j7l"}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.1.0-alpha.5_1705912894061_0.6009708032508447"},"_hasShrinkwrap":false},"1.1.0-alpha.6":{"name":"l-fen","version":"1.1.0-alpha.6","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.2.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"types":"./lib/index.d.ts","readme":"## Description\n\n**l-fen** is a small runtime to execute a bundle of Serverless Functions like they were running in the cloud.\n\n:warning: only [GCP](https://cloud.google.com/) supported right now\n\n## Motivation\n\nWhen working on backend services and applications built by a multitude of individual Serverless Functions with various triggers, like HTTP, Pub/Sub or Storage/Bucket-Events, I always struggled with testing the whole setup locally.  \nEven if there's a 100% test coverage for each function itself, one function of the whole application might expect a different event than another function publishes.\n\nSo my approach was always to test the code itself with unit and e2e tests, deploy to a test or dev environment and test the whole process of the application again. If something fails, I had to look into the logs, hoping that I logged enough to narrow down the problem and ideally get the line where my code broke or wasn't working as expected.\n\nAfter a while, I decided to write something like an emulator for the various triggers and events.  \nThe goal was to locally run my backend service, which had several HTTP functions writing files to buckets, triggering other functions, publishing events via Pub/Sub with other functions subscribing to these.\n\nIn the end, I came up with **l-fen**, the _local function environment_.\n\n## Usage\n\nFirst, you have to build your Functions, since **l-fen** `requires()` the JS file.\n\nWhen that's done, you can start to create your `.fenrc.json` file.  \nThis file contains the whole configuration of your setup and the individual Functions.\nIt's required to to tell **l-fen** which Functions to load and when to call these.\n\nWhen everything is set up, just call `npx l-fen` to do it's magic.\n\n### Configuration\n\n```jsonc\n{\n  // the local folder containg all buckets and their files\n  \"storageDir\": \"./local-test-storage\",\n  // the port of the proxy, handling all HTTP requests and forwarding to the related HTTP Functions\n  \"httpProxyPort\": \"8080\",\n  // the port of the gRPC server handling stuff like Pub/Sub\n  \"grpcProxyPort\": \"50051\",\n  // the log level for l-fen. To print logs, created by Functions, set it to \"verbose\" or lower\n  \"loglevel\": \"verbose\",\n  // configuration for the Cloud Provider\n  \"provider\": {\n    // the Cloud Provider itself, right now, only GCP is supported\n    \"name\": \"GCP\",\n    // name of the project, used for some internal authentication processes\n    \"project\": \"my-test-project\"\n  },\n  // list of all Functions to be managed by l-fen\n  \"services\": [\n    {\n      // name of the Function, in case of HTTP Functions, it serves as the base route for the proxy\n      \"name\": \"http-handler\",\n      // path to the built main JS file\n      \"path\": \"./http-handler/index.js\",\n      // the exported entry point of the function\n      \"entryPoint\": \"handler\",\n      // the type of the function, \"HTTP\", \"PUBSUB\" or \"BUCKET\"\n      \"triggerType\": \"HTTP\",\n      // environment variables to be used by the Function\n      \"envVars\": {\n        \"PUBSUB_API_ENDPOINT\": \"localhost:50051\",\n        \"PROJECT_ID\": \"my-test-project\",\n        \"PUBSUB_TOPIC\": \"generic-topic\"\n      }\n    },\n    {\n      \"name\": \"file-writer\",\n      \"path\": \"./file-writer/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"PUBSUB\",\n      // if triggerType is either \"PUBSUB\" or \"BUCKET\", additional configuration has to be done here.\n      \"triggerConfig\": {\n        // the topic, the Pub/Sub function should subscribe\n        \"topic\": \"generic-topic\"\n      },\n      \"envVars\": {\n        \"MY_BUCKET\": \"my-bucket\"\n      }\n    },\n    {\n      \"name\": \"file-reader\",\n      \"path\": \"./file-reader/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"BUCKET\",\n      \"triggerConfig\": {\n        // file events the Function should listen on. Right now, only \"WRITE\" is supported\n        \"events\": [\"WRITE\"],\n        // the bucket the Function should listen on.\n        \"targetBucket\": \"my-bucket\"\n      },\n      \"envVars\": {}\n    }\n  ],\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n\n## Restrictions\n\n### Cloud Providers\n\nRight now, **l-fen** is only capable of handling Google Cloud Functions.  \nOther Cloud Providers are not yet supported.\n\n### Pub/Sub\n\nThe original idea was, to use **l-fen** out of the box, without the need of changing the Functions code to work with **l-fen**.\nUnfortunately, I didn't achieve that completely for Pub/Sub.\n\nTo allow **l-fen** to handle published event, the publisher has to use the gRPC server provided by **l-fen**.  \nWhen using the [Google Cloud Pub/Sub:Node.js Client](https://www.npmjs.com/package/@google-cloud/pubsub), the endpoint can be changed by instantiating the client with `new PubSub({apiEndpoint: 'localhost:50051'})`.\n\nI suggest to introduce an environment variable to the Function publishing events, to dynamically override the default endpoint and set that environment variable within the functions configuration in the `.fenrc.json`.\n\n## Under the hood\n\n**l-fen** consists of two main parts, the **orchestrator** and the **bootloader**, the brain and the heart of **l-fen**.\n\nThe orchestrator, the brain, plans the whole environment and knows what to do in certain cases and events.\nThe bootloader, the heart, is executing the Functions and providing all necessary data.\n\nThe orchestrator spawns new bootloader processes by executing the bootloader with required arguments.\n\n### The orchestrator\n\nThe orchestrator is responsible for loading the `.fenrc.json`, managing the Functions and setting up the local environment itself.\n\nDepending on the type of Functions, it adds a file watcher to the `storageDir` and calls the bootloader of the Functions that might be triggered by certain events in buckets.  \nThe same applies to HTTP and Pub/Sub Functions. The orchestrator provides a HTTP proxy and forwards any requests to the responsible Functions. It also starts the gRPC server that can be used by Pub/Sub to handle events and topics and call the according Functions.\n\n### The bootloader\n\nThe bootloader is responsible for starting a Function and providing all the required stuff the Function needs.\nIt's really nothing else like a small CLI tool, that expects certain arguments, like the payload it should forward to the Function or the environment variables the Function should be provided with.\n\nFor bucket and Pub/Sub Functions, it's quite straight forward. It `requires()` the Functions JS file, mapping the payload to the corresponding object and calling the `entryPoint` method with that object.\n\nIn case of an HTTP Function, the bootloader makes use of [express](https://www.npmjs.com/package/express) and [SuperTest](https://www.npmjs.com/package/supertest) create a proper request and handle stuff like different HTTP methods and headers.\n\nThe orchestrator is loading the `.fenrc.json`, managing all Functions, providing some basic setup like configuring a fake service account and file watcher on the specified `storageDir` and start the HTTP proxy.\n\nIt's also possible to start the bootloader without the orchestrator by running something like\n`npx l-fen-boot --event='{\\\"bucket\\\": \\\"user\\\", \\\"name\\\": \\\"111/b.json\\\"}' --path=\"../../../path/to/function/index.js\" --entryPoint=\"handleEvent\" --name=\"name-of-the-function\"`\n\n### Custom extension\n\nl-fen allows adding custom extensions that are executed for each loaded Function right before starting it.\n\nCreate your extension by exporting a class implementing the `LFenExtension` interface from `l-fen/types/extension.interface` and adding the absolute path to the config:\n\n```jsonc\n{\n  // all the other stuff, see above\n  ...\n  // here we add out extension\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n","readmeFilename":"README.md","gitHead":"10f017fc49dafdb22619a6246967517568ad39ad","_id":"l-fen@1.1.0-alpha.6","_nodeVersion":"18.19.0","_npmVersion":"9.6.3","dist":{"integrity":"sha512-hVz/JLKMTKVfQ05v03sgTh2wndyciBW12vorCDf1/MpOsXDr0cF0agqDtc013j7JmmRKpX+U26LrdiadbTg+KQ==","shasum":"98197b42a77cf29255b34a52322968c8f92f0109","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.1.0-alpha.6.tgz","fileCount":102,"unpackedSize":1395077,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCICerw+iIsXb7hjMRdpm6J3plQXat1B4Y/0of2WCXwp5gAiBS91AuHApAVVonsz9TPi7kLFYgen2f+OAWZsA169xTfg=="}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.1.0-alpha.6_1705915221257_0.8201292353640641"},"_hasShrinkwrap":false},"1.1.0-alpha.7":{"name":"l-fen","version":"1.1.0-alpha.7","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.2.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"types":"./lib/index.d.ts","readme":"## Description\n\n**l-fen** is a small runtime to execute a bundle of Serverless Functions like they were running in the cloud.\n\n:warning: only [GCP](https://cloud.google.com/) supported right now\n\n## Motivation\n\nWhen working on backend services and applications built by a multitude of individual Serverless Functions with various triggers, like HTTP, Pub/Sub or Storage/Bucket-Events, I always struggled with testing the whole setup locally.  \nEven if there's a 100% test coverage for each function itself, one function of the whole application might expect a different event than another function publishes.\n\nSo my approach was always to test the code itself with unit and e2e tests, deploy to a test or dev environment and test the whole process of the application again. If something fails, I had to look into the logs, hoping that I logged enough to narrow down the problem and ideally get the line where my code broke or wasn't working as expected.\n\nAfter a while, I decided to write something like an emulator for the various triggers and events.  \nThe goal was to locally run my backend service, which had several HTTP functions writing files to buckets, triggering other functions, publishing events via Pub/Sub with other functions subscribing to these.\n\nIn the end, I came up with **l-fen**, the _local function environment_.\n\n## Usage\n\nFirst, you have to build your Functions, since **l-fen** `requires()` the JS file.\n\nWhen that's done, you can start to create your `.fenrc.json` file.  \nThis file contains the whole configuration of your setup and the individual Functions.\nIt's required to to tell **l-fen** which Functions to load and when to call these.\n\nWhen everything is set up, just call `npx l-fen` to do it's magic.\n\n### Configuration\n\n```jsonc\n{\n  // the local folder containg all buckets and their files\n  \"storageDir\": \"./local-test-storage\",\n  // the port of the proxy, handling all HTTP requests and forwarding to the related HTTP Functions\n  \"httpProxyPort\": \"8080\",\n  // the port of the gRPC server handling stuff like Pub/Sub\n  \"grpcProxyPort\": \"50051\",\n  // the log level for l-fen. To print logs, created by Functions, set it to \"verbose\" or lower\n  \"loglevel\": \"verbose\",\n  // configuration for the Cloud Provider\n  \"provider\": {\n    // the Cloud Provider itself, right now, only GCP is supported\n    \"name\": \"GCP\",\n    // name of the project, used for some internal authentication processes\n    \"project\": \"my-test-project\"\n  },\n  // list of all Functions to be managed by l-fen\n  \"services\": [\n    {\n      // name of the Function, in case of HTTP Functions, it serves as the base route for the proxy\n      \"name\": \"http-handler\",\n      // path to the built main JS file\n      \"path\": \"./http-handler/index.js\",\n      // the exported entry point of the function\n      \"entryPoint\": \"handler\",\n      // the type of the function, \"HTTP\", \"PUBSUB\" or \"BUCKET\"\n      \"triggerType\": \"HTTP\",\n      // environment variables to be used by the Function\n      \"envVars\": {\n        \"PUBSUB_API_ENDPOINT\": \"localhost:50051\",\n        \"PROJECT_ID\": \"my-test-project\",\n        \"PUBSUB_TOPIC\": \"generic-topic\"\n      }\n    },\n    {\n      \"name\": \"file-writer\",\n      \"path\": \"./file-writer/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"PUBSUB\",\n      // if triggerType is either \"PUBSUB\" or \"BUCKET\", additional configuration has to be done here.\n      \"triggerConfig\": {\n        // the topic, the Pub/Sub function should subscribe\n        \"topic\": \"generic-topic\"\n      },\n      \"envVars\": {\n        \"MY_BUCKET\": \"my-bucket\"\n      }\n    },\n    {\n      \"name\": \"file-reader\",\n      \"path\": \"./file-reader/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"BUCKET\",\n      \"triggerConfig\": {\n        // file events the Function should listen on. Right now, only \"WRITE\" is supported\n        \"events\": [\"WRITE\"],\n        // the bucket the Function should listen on.\n        \"targetBucket\": \"my-bucket\"\n      },\n      \"envVars\": {}\n    }\n  ],\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n\n## Restrictions\n\n### Cloud Providers\n\nRight now, **l-fen** is only capable of handling Google Cloud Functions.  \nOther Cloud Providers are not yet supported.\n\n### Pub/Sub\n\nThe original idea was, to use **l-fen** out of the box, without the need of changing the Functions code to work with **l-fen**.\nUnfortunately, I didn't achieve that completely for Pub/Sub.\n\nTo allow **l-fen** to handle published event, the publisher has to use the gRPC server provided by **l-fen**.  \nWhen using the [Google Cloud Pub/Sub:Node.js Client](https://www.npmjs.com/package/@google-cloud/pubsub), the endpoint can be changed by instantiating the client with `new PubSub({apiEndpoint: 'localhost:50051'})`.\n\nI suggest to introduce an environment variable to the Function publishing events, to dynamically override the default endpoint and set that environment variable within the functions configuration in the `.fenrc.json`.\n\n## Under the hood\n\n**l-fen** consists of two main parts, the **orchestrator** and the **bootloader**, the brain and the heart of **l-fen**.\n\nThe orchestrator, the brain, plans the whole environment and knows what to do in certain cases and events.\nThe bootloader, the heart, is executing the Functions and providing all necessary data.\n\nThe orchestrator spawns new bootloader processes by executing the bootloader with required arguments.\n\n### The orchestrator\n\nThe orchestrator is responsible for loading the `.fenrc.json`, managing the Functions and setting up the local environment itself.\n\nDepending on the type of Functions, it adds a file watcher to the `storageDir` and calls the bootloader of the Functions that might be triggered by certain events in buckets.  \nThe same applies to HTTP and Pub/Sub Functions. The orchestrator provides a HTTP proxy and forwards any requests to the responsible Functions. It also starts the gRPC server that can be used by Pub/Sub to handle events and topics and call the according Functions.\n\n### The bootloader\n\nThe bootloader is responsible for starting a Function and providing all the required stuff the Function needs.\nIt's really nothing else like a small CLI tool, that expects certain arguments, like the payload it should forward to the Function or the environment variables the Function should be provided with.\n\nFor bucket and Pub/Sub Functions, it's quite straight forward. It `requires()` the Functions JS file, mapping the payload to the corresponding object and calling the `entryPoint` method with that object.\n\nIn case of an HTTP Function, the bootloader makes use of [express](https://www.npmjs.com/package/express) and [SuperTest](https://www.npmjs.com/package/supertest) create a proper request and handle stuff like different HTTP methods and headers.\n\nThe orchestrator is loading the `.fenrc.json`, managing all Functions, providing some basic setup like configuring a fake service account and file watcher on the specified `storageDir` and start the HTTP proxy.\n\nIt's also possible to start the bootloader without the orchestrator by running something like\n`npx l-fen-boot --event='{\\\"bucket\\\": \\\"user\\\", \\\"name\\\": \\\"111/b.json\\\"}' --path=\"../../../path/to/function/index.js\" --entryPoint=\"handleEvent\" --name=\"name-of-the-function\"`\n\n### Custom extension\n\nl-fen allows adding custom extensions that are executed for each loaded Function right before starting it.\n\nCreate your extension by exporting a class implementing the `LFenExtension` interface from `l-fen/types/extension.interface` and adding the absolute path to the config:\n\n```jsonc\n{\n  // all the other stuff, see above\n  ...\n  // here we add out extension\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n","readmeFilename":"README.md","gitHead":"e92e69ad2e7829159d25d277b22c0db2cfefc2d4","_id":"l-fen@1.1.0-alpha.7","_nodeVersion":"18.19.0","_npmVersion":"9.6.3","dist":{"integrity":"sha512-TjaaHtZdh+X95cRcAtJWGfsY8o5p2sFHSyPTvPMK4cpI0kf9uV6AyZX0Ag6JrM6YFc9Vw+LXIK1Dp1iOG1nTnw==","shasum":"df1b229d4bda6f650372e836fbe19f87a175cfb1","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.1.0-alpha.7.tgz","fileCount":102,"unpackedSize":1395741,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCR3NAoL0gWUZvXqlRX0eIIpuXrBHKDieBSYuK0e6o13QIhAPIXBoMbD2PP+FPA9r3YqPnFtsELq3EItN+rd3jtceI8"}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.1.0-alpha.7_1705919233547_0.3461674178521641"},"_hasShrinkwrap":false},"1.1.0-alpha.8":{"name":"l-fen","version":"1.1.0-alpha.8","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.2.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"types":"./lib/index.d.ts","readme":"## Description\n\n**l-fen** is a small runtime to execute a bundle of Serverless Functions like they were running in the cloud.\n\n:warning: only [GCP](https://cloud.google.com/) supported right now\n\n## Motivation\n\nWhen working on backend services and applications built by a multitude of individual Serverless Functions with various triggers, like HTTP, Pub/Sub or Storage/Bucket-Events, I always struggled with testing the whole setup locally.  \nEven if there's a 100% test coverage for each function itself, one function of the whole application might expect a different event than another function publishes.\n\nSo my approach was always to test the code itself with unit and e2e tests, deploy to a test or dev environment and test the whole process of the application again. If something fails, I had to look into the logs, hoping that I logged enough to narrow down the problem and ideally get the line where my code broke or wasn't working as expected.\n\nAfter a while, I decided to write something like an emulator for the various triggers and events.  \nThe goal was to locally run my backend service, which had several HTTP functions writing files to buckets, triggering other functions, publishing events via Pub/Sub with other functions subscribing to these.\n\nIn the end, I came up with **l-fen**, the _local function environment_.\n\n## Usage\n\nFirst, you have to build your Functions, since **l-fen** `requires()` the JS file.\n\nWhen that's done, you can start to create your `.fenrc.json` file.  \nThis file contains the whole configuration of your setup and the individual Functions.\nIt's required to to tell **l-fen** which Functions to load and when to call these.\n\nWhen everything is set up, just call `npx l-fen` to do it's magic.\n\n### Configuration\n\n```jsonc\n{\n  // the local folder containg all buckets and their files\n  \"storageDir\": \"./local-test-storage\",\n  // the port of the proxy, handling all HTTP requests and forwarding to the related HTTP Functions\n  \"httpProxyPort\": \"8080\",\n  // the port of the gRPC server handling stuff like Pub/Sub\n  \"grpcProxyPort\": \"50051\",\n  // the log level for l-fen. To print logs, created by Functions, set it to \"verbose\" or lower\n  \"loglevel\": \"verbose\",\n  // configuration for the Cloud Provider\n  \"provider\": {\n    // the Cloud Provider itself, right now, only GCP is supported\n    \"name\": \"GCP\",\n    // name of the project, used for some internal authentication processes\n    \"project\": \"my-test-project\"\n  },\n  // list of all Functions to be managed by l-fen\n  \"services\": [\n    {\n      // name of the Function, in case of HTTP Functions, it serves as the base route for the proxy\n      \"name\": \"http-handler\",\n      // path to the built main JS file\n      \"path\": \"./http-handler/index.js\",\n      // the exported entry point of the function\n      \"entryPoint\": \"handler\",\n      // the type of the function, \"HTTP\", \"PUBSUB\" or \"BUCKET\"\n      \"triggerType\": \"HTTP\",\n      // environment variables to be used by the Function\n      \"envVars\": {\n        \"PUBSUB_API_ENDPOINT\": \"localhost:50051\",\n        \"PROJECT_ID\": \"my-test-project\",\n        \"PUBSUB_TOPIC\": \"generic-topic\"\n      }\n    },\n    {\n      \"name\": \"file-writer\",\n      \"path\": \"./file-writer/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"PUBSUB\",\n      // if triggerType is either \"PUBSUB\" or \"BUCKET\", additional configuration has to be done here.\n      \"triggerConfig\": {\n        // the topic, the Pub/Sub function should subscribe\n        \"topic\": \"generic-topic\"\n      },\n      \"envVars\": {\n        \"MY_BUCKET\": \"my-bucket\"\n      }\n    },\n    {\n      \"name\": \"file-reader\",\n      \"path\": \"./file-reader/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"BUCKET\",\n      \"triggerConfig\": {\n        // file events the Function should listen on. Right now, only \"WRITE\" is supported\n        \"events\": [\"WRITE\"],\n        // the bucket the Function should listen on.\n        \"targetBucket\": \"my-bucket\"\n      },\n      \"envVars\": {}\n    }\n  ],\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n\n## Restrictions\n\n### Cloud Providers\n\nRight now, **l-fen** is only capable of handling Google Cloud Functions.  \nOther Cloud Providers are not yet supported.\n\n### Pub/Sub\n\nThe original idea was, to use **l-fen** out of the box, without the need of changing the Functions code to work with **l-fen**.\nUnfortunately, I didn't achieve that completely for Pub/Sub.\n\nTo allow **l-fen** to handle published event, the publisher has to use the gRPC server provided by **l-fen**.  \nWhen using the [Google Cloud Pub/Sub:Node.js Client](https://www.npmjs.com/package/@google-cloud/pubsub), the endpoint can be changed by instantiating the client with `new PubSub({apiEndpoint: 'localhost:50051'})`.\n\nI suggest to introduce an environment variable to the Function publishing events, to dynamically override the default endpoint and set that environment variable within the functions configuration in the `.fenrc.json`.\n\n## Under the hood\n\n**l-fen** consists of two main parts, the **orchestrator** and the **bootloader**, the brain and the heart of **l-fen**.\n\nThe orchestrator, the brain, plans the whole environment and knows what to do in certain cases and events.\nThe bootloader, the heart, is executing the Functions and providing all necessary data.\n\nThe orchestrator spawns new bootloader processes by executing the bootloader with required arguments.\n\n### The orchestrator\n\nThe orchestrator is responsible for loading the `.fenrc.json`, managing the Functions and setting up the local environment itself.\n\nDepending on the type of Functions, it adds a file watcher to the `storageDir` and calls the bootloader of the Functions that might be triggered by certain events in buckets.  \nThe same applies to HTTP and Pub/Sub Functions. The orchestrator provides a HTTP proxy and forwards any requests to the responsible Functions. It also starts the gRPC server that can be used by Pub/Sub to handle events and topics and call the according Functions.\n\n### The bootloader\n\nThe bootloader is responsible for starting a Function and providing all the required stuff the Function needs.\nIt's really nothing else like a small CLI tool, that expects certain arguments, like the payload it should forward to the Function or the environment variables the Function should be provided with.\n\nFor bucket and Pub/Sub Functions, it's quite straight forward. It `requires()` the Functions JS file, mapping the payload to the corresponding object and calling the `entryPoint` method with that object.\n\nIn case of an HTTP Function, the bootloader makes use of [express](https://www.npmjs.com/package/express) and [SuperTest](https://www.npmjs.com/package/supertest) create a proper request and handle stuff like different HTTP methods and headers.\n\nThe orchestrator is loading the `.fenrc.json`, managing all Functions, providing some basic setup like configuring a fake service account and file watcher on the specified `storageDir` and start the HTTP proxy.\n\nIt's also possible to start the bootloader without the orchestrator by running something like\n`npx l-fen-boot --event='{\\\"bucket\\\": \\\"user\\\", \\\"name\\\": \\\"111/b.json\\\"}' --path=\"../../../path/to/function/index.js\" --entryPoint=\"handleEvent\" --name=\"name-of-the-function\"`\n\n### Custom extension\n\nl-fen allows adding custom extensions that are executed for each loaded Function right before starting it.\n\nCreate your extension by exporting a class implementing the `LFenExtension` interface from `l-fen/types/extension.interface` and adding the absolute path to the config:\n\n```jsonc\n{\n  // all the other stuff, see above\n  ...\n  // here we add out extension\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n","readmeFilename":"README.md","gitHead":"b3884c298d6ba17150296f55b3ddbf5fac9a7b77","_id":"l-fen@1.1.0-alpha.8","_nodeVersion":"18.19.0","_npmVersion":"9.6.3","dist":{"integrity":"sha512-f6aITXnOsq5j1U4RtI/FIEE4wGBz1QdbhbPgSmmg3OZQ5KDrHdmTQAqX3pOmo7UXyU2hPc6Lt/yaNW9vTEi7Iw==","shasum":"d4ebaf0c95edc1a6312e0064ef7d5ac4d2257ccf","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.1.0-alpha.8.tgz","fileCount":102,"unpackedSize":1395849,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQCCBZs4iG7N6Qvn2gyKXDR9Tedw8JnRUU3zvYTb/R/VDwIgZIYWTeqwA3PE0gRW5ioc7+x328j4H4JpeKqYbdXPdHw="}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.1.0-alpha.8_1705924671447_0.954347260036337"},"_hasShrinkwrap":false},"1.1.0-alpha.9":{"name":"l-fen","version":"1.1.0-alpha.9","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.2.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"types":"./lib/index.d.ts","readme":"## Description\n\n**l-fen** is a small runtime to execute a bundle of Serverless Functions like they were running in the cloud.\n\n:warning: only [GCP](https://cloud.google.com/) supported right now\n\n## Motivation\n\nWhen working on backend services and applications built by a multitude of individual Serverless Functions with various triggers, like HTTP, Pub/Sub or Storage/Bucket-Events, I always struggled with testing the whole setup locally.  \nEven if there's a 100% test coverage for each function itself, one function of the whole application might expect a different event than another function publishes.\n\nSo my approach was always to test the code itself with unit and e2e tests, deploy to a test or dev environment and test the whole process of the application again. If something fails, I had to look into the logs, hoping that I logged enough to narrow down the problem and ideally get the line where my code broke or wasn't working as expected.\n\nAfter a while, I decided to write something like an emulator for the various triggers and events.  \nThe goal was to locally run my backend service, which had several HTTP functions writing files to buckets, triggering other functions, publishing events via Pub/Sub with other functions subscribing to these.\n\nIn the end, I came up with **l-fen**, the _local function environment_.\n\n## Usage\n\nFirst, you have to build your Functions, since **l-fen** `requires()` the JS file.\n\nWhen that's done, you can start to create your `.fenrc.json` file.  \nThis file contains the whole configuration of your setup and the individual Functions.\nIt's required to to tell **l-fen** which Functions to load and when to call these.\n\nWhen everything is set up, just call `npx l-fen` to do it's magic.\n\n### Configuration\n\n```jsonc\n{\n  // the local folder containg all buckets and their files\n  \"storageDir\": \"./local-test-storage\",\n  // the port of the proxy, handling all HTTP requests and forwarding to the related HTTP Functions\n  \"httpProxyPort\": \"8080\",\n  // the port of the gRPC server handling stuff like Pub/Sub\n  \"grpcProxyPort\": \"50051\",\n  // the log level for l-fen. To print logs, created by Functions, set it to \"verbose\" or lower\n  \"loglevel\": \"verbose\",\n  // configuration for the Cloud Provider\n  \"provider\": {\n    // the Cloud Provider itself, right now, only GCP is supported\n    \"name\": \"GCP\",\n    // name of the project, used for some internal authentication processes\n    \"project\": \"my-test-project\"\n  },\n  // list of all Functions to be managed by l-fen\n  \"services\": [\n    {\n      // name of the Function, in case of HTTP Functions, it serves as the base route for the proxy\n      \"name\": \"http-handler\",\n      // path to the built main JS file\n      \"path\": \"./http-handler/index.js\",\n      // the exported entry point of the function\n      \"entryPoint\": \"handler\",\n      // the type of the function, \"HTTP\", \"PUBSUB\" or \"BUCKET\"\n      \"triggerType\": \"HTTP\",\n      // environment variables to be used by the Function\n      \"envVars\": {\n        \"PUBSUB_API_ENDPOINT\": \"localhost:50051\",\n        \"PROJECT_ID\": \"my-test-project\",\n        \"PUBSUB_TOPIC\": \"generic-topic\"\n      }\n    },\n    {\n      \"name\": \"file-writer\",\n      \"path\": \"./file-writer/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"PUBSUB\",\n      // if triggerType is either \"PUBSUB\" or \"BUCKET\", additional configuration has to be done here.\n      \"triggerConfig\": {\n        // the topic, the Pub/Sub function should subscribe\n        \"topic\": \"generic-topic\"\n      },\n      \"envVars\": {\n        \"MY_BUCKET\": \"my-bucket\"\n      }\n    },\n    {\n      \"name\": \"file-reader\",\n      \"path\": \"./file-reader/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"BUCKET\",\n      \"triggerConfig\": {\n        // file events the Function should listen on. Right now, only \"WRITE\" is supported\n        \"events\": [\"WRITE\"],\n        // the bucket the Function should listen on.\n        \"targetBucket\": \"my-bucket\"\n      },\n      \"envVars\": {}\n    }\n  ],\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n\n## Restrictions\n\n### Cloud Providers\n\nRight now, **l-fen** is only capable of handling Google Cloud Functions.  \nOther Cloud Providers are not yet supported.\n\n### Pub/Sub\n\nThe original idea was, to use **l-fen** out of the box, without the need of changing the Functions code to work with **l-fen**.\nUnfortunately, I didn't achieve that completely for Pub/Sub.\n\nTo allow **l-fen** to handle published event, the publisher has to use the gRPC server provided by **l-fen**.  \nWhen using the [Google Cloud Pub/Sub:Node.js Client](https://www.npmjs.com/package/@google-cloud/pubsub), the endpoint can be changed by instantiating the client with `new PubSub({apiEndpoint: 'localhost:50051'})`.\n\nI suggest to introduce an environment variable to the Function publishing events, to dynamically override the default endpoint and set that environment variable within the functions configuration in the `.fenrc.json`.\n\n## Under the hood\n\n**l-fen** consists of two main parts, the **orchestrator** and the **bootloader**, the brain and the heart of **l-fen**.\n\nThe orchestrator, the brain, plans the whole environment and knows what to do in certain cases and events.\nThe bootloader, the heart, is executing the Functions and providing all necessary data.\n\nThe orchestrator spawns new bootloader processes by executing the bootloader with required arguments.\n\n### The orchestrator\n\nThe orchestrator is responsible for loading the `.fenrc.json`, managing the Functions and setting up the local environment itself.\n\nDepending on the type of Functions, it adds a file watcher to the `storageDir` and calls the bootloader of the Functions that might be triggered by certain events in buckets.  \nThe same applies to HTTP and Pub/Sub Functions. The orchestrator provides a HTTP proxy and forwards any requests to the responsible Functions. It also starts the gRPC server that can be used by Pub/Sub to handle events and topics and call the according Functions.\n\n### The bootloader\n\nThe bootloader is responsible for starting a Function and providing all the required stuff the Function needs.\nIt's really nothing else like a small CLI tool, that expects certain arguments, like the payload it should forward to the Function or the environment variables the Function should be provided with.\n\nFor bucket and Pub/Sub Functions, it's quite straight forward. It `requires()` the Functions JS file, mapping the payload to the corresponding object and calling the `entryPoint` method with that object.\n\nIn case of an HTTP Function, the bootloader makes use of [express](https://www.npmjs.com/package/express) and [SuperTest](https://www.npmjs.com/package/supertest) create a proper request and handle stuff like different HTTP methods and headers.\n\nThe orchestrator is loading the `.fenrc.json`, managing all Functions, providing some basic setup like configuring a fake service account and file watcher on the specified `storageDir` and start the HTTP proxy.\n\nIt's also possible to start the bootloader without the orchestrator by running something like\n`npx l-fen-boot --event='{\\\"bucket\\\": \\\"user\\\", \\\"name\\\": \\\"111/b.json\\\"}' --path=\"../../../path/to/function/index.js\" --entryPoint=\"handleEvent\" --name=\"name-of-the-function\"`\n\n### Custom extension\n\nl-fen allows adding custom extensions that are executed for each loaded Function right before starting it.\n\nCreate your extension by exporting a class implementing the `LFenExtension` interface from `l-fen/types/extension.interface` and adding the absolute path to the config:\n\n```jsonc\n{\n  // all the other stuff, see above\n  ...\n  // here we add out extension\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n","readmeFilename":"README.md","gitHead":"636d91450c4c0d7f75f10699b1d386101f84683c","_id":"l-fen@1.1.0-alpha.9","_nodeVersion":"18.19.0","_npmVersion":"9.6.3","dist":{"integrity":"sha512-C4rvbQ26mhsIkXaJOqEv2iDibUc/LvCnXAq9HZ3Bh61XYKyUvmzTBNa1yTKS3IMyAsSIn0E6GTGjxzJf5SDg3w==","shasum":"eef345ee55fc065225b4a93cac0ce83cdce0a307","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.1.0-alpha.9.tgz","fileCount":102,"unpackedSize":1396350,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDEu8W0vBLchDPJ6pj9r82hfyZ8tov6J53OOWsIMgq12wIhAMrbcdAIJ8AjlPKYOSiJqDkq/5GEGKwD8rIgXDniob/7"}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.1.0-alpha.9_1705945129477_0.20530067369256355"},"_hasShrinkwrap":false},"1.1.0-alpha.10":{"name":"l-fen","version":"1.1.0-alpha.10","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.2.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"types":"./lib/index.d.ts","readme":"## Description\n\n**l-fen** is a small runtime to execute a bundle of Serverless Functions like they were running in the cloud.\n\n:warning: only [GCP](https://cloud.google.com/) supported right now\n\n## Motivation\n\nWhen working on backend services and applications built by a multitude of individual Serverless Functions with various triggers, like HTTP, Pub/Sub or Storage/Bucket-Events, I always struggled with testing the whole setup locally.  \nEven if there's a 100% test coverage for each function itself, one function of the whole application might expect a different event than another function publishes.\n\nSo my approach was always to test the code itself with unit and e2e tests, deploy to a test or dev environment and test the whole process of the application again. If something fails, I had to look into the logs, hoping that I logged enough to narrow down the problem and ideally get the line where my code broke or wasn't working as expected.\n\nAfter a while, I decided to write something like an emulator for the various triggers and events.  \nThe goal was to locally run my backend service, which had several HTTP functions writing files to buckets, triggering other functions, publishing events via Pub/Sub with other functions subscribing to these.\n\nIn the end, I came up with **l-fen**, the _local function environment_.\n\n## Usage\n\nFirst, you have to build your Functions, since **l-fen** `requires()` the JS file.\n\nWhen that's done, you can start to create your `.fenrc.json` file.  \nThis file contains the whole configuration of your setup and the individual Functions.\nIt's required to to tell **l-fen** which Functions to load and when to call these.\n\nWhen everything is set up, just call `npx l-fen` to do it's magic.\n\n### Configuration\n\n```jsonc\n{\n  // the local folder containg all buckets and their files\n  \"storageDir\": \"./local-test-storage\",\n  // the port of the proxy, handling all HTTP requests and forwarding to the related HTTP Functions\n  \"httpProxyPort\": \"8080\",\n  // the port of the gRPC server handling stuff like Pub/Sub\n  \"grpcProxyPort\": \"50051\",\n  // the log level for l-fen. To print logs, created by Functions, set it to \"verbose\" or lower\n  \"loglevel\": \"verbose\",\n  // configuration for the Cloud Provider\n  \"provider\": {\n    // the Cloud Provider itself, right now, only GCP is supported\n    \"name\": \"GCP\",\n    // name of the project, used for some internal authentication processes\n    \"project\": \"my-test-project\"\n  },\n  // list of all Functions to be managed by l-fen\n  \"services\": [\n    {\n      // name of the Function, in case of HTTP Functions, it serves as the base route for the proxy\n      \"name\": \"http-handler\",\n      // path to the built main JS file\n      \"path\": \"./http-handler/index.js\",\n      // the exported entry point of the function\n      \"entryPoint\": \"handler\",\n      // the type of the function, \"HTTP\", \"PUBSUB\" or \"BUCKET\"\n      \"triggerType\": \"HTTP\",\n      // environment variables to be used by the Function\n      \"envVars\": {\n        \"PUBSUB_API_ENDPOINT\": \"localhost:50051\",\n        \"PROJECT_ID\": \"my-test-project\",\n        \"PUBSUB_TOPIC\": \"generic-topic\"\n      }\n    },\n    {\n      \"name\": \"file-writer\",\n      \"path\": \"./file-writer/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"PUBSUB\",\n      // if triggerType is either \"PUBSUB\" or \"BUCKET\", additional configuration has to be done here.\n      \"triggerConfig\": {\n        // the topic, the Pub/Sub function should subscribe\n        \"topic\": \"generic-topic\"\n      },\n      \"envVars\": {\n        \"MY_BUCKET\": \"my-bucket\"\n      }\n    },\n    {\n      \"name\": \"file-reader\",\n      \"path\": \"./file-reader/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"BUCKET\",\n      \"triggerConfig\": {\n        // file events the Function should listen on. Right now, only \"WRITE\" is supported\n        \"events\": [\"WRITE\"],\n        // the bucket the Function should listen on.\n        \"targetBucket\": \"my-bucket\"\n      },\n      \"envVars\": {}\n    }\n  ],\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n\n## Restrictions\n\n### Cloud Providers\n\nRight now, **l-fen** is only capable of handling Google Cloud Functions.  \nOther Cloud Providers are not yet supported.\n\n### Pub/Sub\n\nThe original idea was, to use **l-fen** out of the box, without the need of changing the Functions code to work with **l-fen**.\nUnfortunately, I didn't achieve that completely for Pub/Sub.\n\nTo allow **l-fen** to handle published event, the publisher has to use the gRPC server provided by **l-fen**.  \nWhen using the [Google Cloud Pub/Sub:Node.js Client](https://www.npmjs.com/package/@google-cloud/pubsub), the endpoint can be changed by instantiating the client with `new PubSub({apiEndpoint: 'localhost:50051'})`.\n\nI suggest to introduce an environment variable to the Function publishing events, to dynamically override the default endpoint and set that environment variable within the functions configuration in the `.fenrc.json`.\n\n## Under the hood\n\n**l-fen** consists of two main parts, the **orchestrator** and the **bootloader**, the brain and the heart of **l-fen**.\n\nThe orchestrator, the brain, plans the whole environment and knows what to do in certain cases and events.\nThe bootloader, the heart, is executing the Functions and providing all necessary data.\n\nThe orchestrator spawns new bootloader processes by executing the bootloader with required arguments.\n\n### The orchestrator\n\nThe orchestrator is responsible for loading the `.fenrc.json`, managing the Functions and setting up the local environment itself.\n\nDepending on the type of Functions, it adds a file watcher to the `storageDir` and calls the bootloader of the Functions that might be triggered by certain events in buckets.  \nThe same applies to HTTP and Pub/Sub Functions. The orchestrator provides a HTTP proxy and forwards any requests to the responsible Functions. It also starts the gRPC server that can be used by Pub/Sub to handle events and topics and call the according Functions.\n\n### The bootloader\n\nThe bootloader is responsible for starting a Function and providing all the required stuff the Function needs.\nIt's really nothing else like a small CLI tool, that expects certain arguments, like the payload it should forward to the Function or the environment variables the Function should be provided with.\n\nFor bucket and Pub/Sub Functions, it's quite straight forward. It `requires()` the Functions JS file, mapping the payload to the corresponding object and calling the `entryPoint` method with that object.\n\nIn case of an HTTP Function, the bootloader makes use of [express](https://www.npmjs.com/package/express) and [SuperTest](https://www.npmjs.com/package/supertest) create a proper request and handle stuff like different HTTP methods and headers.\n\nThe orchestrator is loading the `.fenrc.json`, managing all Functions, providing some basic setup like configuring a fake service account and file watcher on the specified `storageDir` and start the HTTP proxy.\n\nIt's also possible to start the bootloader without the orchestrator by running something like\n`npx l-fen-boot --event='{\\\"bucket\\\": \\\"user\\\", \\\"name\\\": \\\"111/b.json\\\"}' --path=\"../../../path/to/function/index.js\" --entryPoint=\"handleEvent\" --name=\"name-of-the-function\"`\n\n### Custom extension\n\nl-fen allows adding custom extensions that are executed for each loaded Function right before starting it.\n\nCreate your extension by exporting a class implementing the `LFenExtension` interface from `l-fen/types/extension.interface` and adding the absolute path to the config:\n\n```jsonc\n{\n  // all the other stuff, see above\n  ...\n  // here we add out extension\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n","readmeFilename":"README.md","gitHead":"567576d323557e81dbc26537f2cad3a91ac0da84","_id":"l-fen@1.1.0-alpha.10","_nodeVersion":"18.19.0","_npmVersion":"9.6.3","dist":{"integrity":"sha512-rgUDNc1RaDd5hzhno7Vx34ez44VR/lQvS9/zMyFsp5WBLFOzPh3PDwbB11qSXb+S7CrV9XpNsUlT1PFHhoDjrQ==","shasum":"a5a4d5ac9c8960538c5d8ba1f16d5eb70bdc6d6b","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.1.0-alpha.10.tgz","fileCount":102,"unpackedSize":1396714,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQCjJ9xLBaWldC7KjroY5Mn2GSPZko6KGpTAmOFwQ1/zQgIhAOOP6FBajNFPxxEbBj0nwacc+vOAxi0wSQnlR9WzyIX2"}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.1.0-alpha.10_1705946047309_0.5630216652326121"},"_hasShrinkwrap":false},"1.1.0-alpha.11":{"name":"l-fen","version":"1.1.0-alpha.11","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.2.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"types":"./lib/index.d.ts","readme":"## Description\n\n**l-fen** is a small runtime to execute a bundle of Serverless Functions like they were running in the cloud.\n\n:warning: only [GCP](https://cloud.google.com/) supported right now\n\n## Motivation\n\nWhen working on backend services and applications built by a multitude of individual Serverless Functions with various triggers, like HTTP, Pub/Sub or Storage/Bucket-Events, I always struggled with testing the whole setup locally.  \nEven if there's a 100% test coverage for each function itself, one function of the whole application might expect a different event than another function publishes.\n\nSo my approach was always to test the code itself with unit and e2e tests, deploy to a test or dev environment and test the whole process of the application again. If something fails, I had to look into the logs, hoping that I logged enough to narrow down the problem and ideally get the line where my code broke or wasn't working as expected.\n\nAfter a while, I decided to write something like an emulator for the various triggers and events.  \nThe goal was to locally run my backend service, which had several HTTP functions writing files to buckets, triggering other functions, publishing events via Pub/Sub with other functions subscribing to these.\n\nIn the end, I came up with **l-fen**, the _local function environment_.\n\n## Usage\n\nFirst, you have to build your Functions, since **l-fen** `requires()` the JS file.\n\nWhen that's done, you can start to create your `.fenrc.json` file.  \nThis file contains the whole configuration of your setup and the individual Functions.\nIt's required to to tell **l-fen** which Functions to load and when to call these.\n\nWhen everything is set up, just call `npx l-fen` to do it's magic.\n\n### Configuration\n\n```jsonc\n{\n  // the local folder containg all buckets and their files\n  \"storageDir\": \"./local-test-storage\",\n  // the port of the proxy, handling all HTTP requests and forwarding to the related HTTP Functions\n  \"httpProxyPort\": \"8080\",\n  // the port of the gRPC server handling stuff like Pub/Sub\n  \"grpcProxyPort\": \"50051\",\n  // the log level for l-fen. To print logs, created by Functions, set it to \"verbose\" or lower\n  \"loglevel\": \"verbose\",\n  // configuration for the Cloud Provider\n  \"provider\": {\n    // the Cloud Provider itself, right now, only GCP is supported\n    \"name\": \"GCP\",\n    // name of the project, used for some internal authentication processes\n    \"project\": \"my-test-project\"\n  },\n  // list of all Functions to be managed by l-fen\n  \"services\": [\n    {\n      // name of the Function, in case of HTTP Functions, it serves as the base route for the proxy\n      \"name\": \"http-handler\",\n      // path to the built main JS file\n      \"path\": \"./http-handler/index.js\",\n      // the exported entry point of the function\n      \"entryPoint\": \"handler\",\n      // the type of the function, \"HTTP\", \"PUBSUB\" or \"BUCKET\"\n      \"triggerType\": \"HTTP\",\n      // environment variables to be used by the Function\n      \"envVars\": {\n        \"PUBSUB_API_ENDPOINT\": \"localhost:50051\",\n        \"PROJECT_ID\": \"my-test-project\",\n        \"PUBSUB_TOPIC\": \"generic-topic\"\n      }\n    },\n    {\n      \"name\": \"file-writer\",\n      \"path\": \"./file-writer/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"PUBSUB\",\n      // if triggerType is either \"PUBSUB\" or \"BUCKET\", additional configuration has to be done here.\n      \"triggerConfig\": {\n        // the topic, the Pub/Sub function should subscribe\n        \"topic\": \"generic-topic\"\n      },\n      \"envVars\": {\n        \"MY_BUCKET\": \"my-bucket\"\n      }\n    },\n    {\n      \"name\": \"file-reader\",\n      \"path\": \"./file-reader/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"BUCKET\",\n      \"triggerConfig\": {\n        // file events the Function should listen on. Right now, only \"WRITE\" is supported\n        \"events\": [\"WRITE\"],\n        // the bucket the Function should listen on.\n        \"targetBucket\": \"my-bucket\"\n      },\n      \"envVars\": {}\n    }\n  ],\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n\n## Restrictions\n\n### Cloud Providers\n\nRight now, **l-fen** is only capable of handling Google Cloud Functions.  \nOther Cloud Providers are not yet supported.\n\n### Pub/Sub\n\nThe original idea was, to use **l-fen** out of the box, without the need of changing the Functions code to work with **l-fen**.\nUnfortunately, I didn't achieve that completely for Pub/Sub.\n\nTo allow **l-fen** to handle published event, the publisher has to use the gRPC server provided by **l-fen**.  \nWhen using the [Google Cloud Pub/Sub:Node.js Client](https://www.npmjs.com/package/@google-cloud/pubsub), the endpoint can be changed by instantiating the client with `new PubSub({apiEndpoint: 'localhost:50051'})`.\n\nI suggest to introduce an environment variable to the Function publishing events, to dynamically override the default endpoint and set that environment variable within the functions configuration in the `.fenrc.json`.\n\n## Under the hood\n\n**l-fen** consists of two main parts, the **orchestrator** and the **bootloader**, the brain and the heart of **l-fen**.\n\nThe orchestrator, the brain, plans the whole environment and knows what to do in certain cases and events.\nThe bootloader, the heart, is executing the Functions and providing all necessary data.\n\nThe orchestrator spawns new bootloader processes by executing the bootloader with required arguments.\n\n### The orchestrator\n\nThe orchestrator is responsible for loading the `.fenrc.json`, managing the Functions and setting up the local environment itself.\n\nDepending on the type of Functions, it adds a file watcher to the `storageDir` and calls the bootloader of the Functions that might be triggered by certain events in buckets.  \nThe same applies to HTTP and Pub/Sub Functions. The orchestrator provides a HTTP proxy and forwards any requests to the responsible Functions. It also starts the gRPC server that can be used by Pub/Sub to handle events and topics and call the according Functions.\n\n### The bootloader\n\nThe bootloader is responsible for starting a Function and providing all the required stuff the Function needs.\nIt's really nothing else like a small CLI tool, that expects certain arguments, like the payload it should forward to the Function or the environment variables the Function should be provided with.\n\nFor bucket and Pub/Sub Functions, it's quite straight forward. It `requires()` the Functions JS file, mapping the payload to the corresponding object and calling the `entryPoint` method with that object.\n\nIn case of an HTTP Function, the bootloader makes use of [express](https://www.npmjs.com/package/express) and [SuperTest](https://www.npmjs.com/package/supertest) create a proper request and handle stuff like different HTTP methods and headers.\n\nThe orchestrator is loading the `.fenrc.json`, managing all Functions, providing some basic setup like configuring a fake service account and file watcher on the specified `storageDir` and start the HTTP proxy.\n\nIt's also possible to start the bootloader without the orchestrator by running something like\n`npx l-fen-boot --event='{\\\"bucket\\\": \\\"user\\\", \\\"name\\\": \\\"111/b.json\\\"}' --path=\"../../../path/to/function/index.js\" --entryPoint=\"handleEvent\" --name=\"name-of-the-function\"`\n\n### Custom extension\n\nl-fen allows adding custom extensions that are executed for each loaded Function right before starting it.\n\nCreate your extension by exporting a class implementing the `LFenExtension` interface from `l-fen/types/extension.interface` and adding the absolute path to the config:\n\n```jsonc\n{\n  // all the other stuff, see above\n  ...\n  // here we add out extension\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n","readmeFilename":"README.md","gitHead":"3b06499e6ccb8ff82e4df2a8000ca76bda4e9502","_id":"l-fen@1.1.0-alpha.11","_nodeVersion":"18.19.0","_npmVersion":"9.6.3","dist":{"integrity":"sha512-MTyxHRLmxMROp5MkWTYYnuOUevB2i2FVwZdm6PT6vU6K+6iZo7SLhpH/GlZ5Ie9K4R3KvxNwFu3SBpdrv5doPQ==","shasum":"5e2730b90a91df472bf0fe257ae91ecebdeb062d","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.1.0-alpha.11.tgz","fileCount":102,"unpackedSize":1397734,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDbxwkTvwzmRBDJ4hv3RI/VINPCJ5qja+JlRAFKT2f74QIhAJtjfc5p20n2Ba3jafm/cVz38caMxdI649jd+YCLtRVN"}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.1.0-alpha.11_1705946987363_0.945065010566325"},"_hasShrinkwrap":false},"1.1.0-alpha.12":{"name":"l-fen","version":"1.1.0-alpha.12","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"postinstall":"npx simple-git-hooks","test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.9.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"types":"./lib/index.d.ts","readme":"## Description\n\n**l-fen** is a small runtime to execute a bundle of Serverless Functions like they were running in the cloud.\n\n:warning: only [GCP](https://cloud.google.com/) supported right now\n\n## Motivation\n\nWhen working on backend services and applications built by a multitude of individual Serverless Functions with various triggers, like HTTP, Pub/Sub or Storage/Bucket-Events, I always struggled with testing the whole setup locally.  \nEven if there's a 100% test coverage for each function itself, one function of the whole application might expect a different event than another function publishes.\n\nSo my approach was always to test the code itself with unit and e2e tests, deploy to a test or dev environment and test the whole process of the application again. If something fails, I had to look into the logs, hoping that I logged enough to narrow down the problem and ideally get the line where my code broke or wasn't working as expected.\n\nAfter a while, I decided to write something like an emulator for the various triggers and events.  \nThe goal was to locally run my backend service, which had several HTTP functions writing files to buckets, triggering other functions, publishing events via Pub/Sub with other functions subscribing to these.\n\nIn the end, I came up with **l-fen**, the _local function environment_.\n\n## Usage\n\nFirst, you have to build your Functions, since **l-fen** `requires()` the JS file.\n\nWhen that's done, you can start to create your `.fenrc.json` file.  \nThis file contains the whole configuration of your setup and the individual Functions.\nIt's required to to tell **l-fen** which Functions to load and when to call these.\n\nWhen everything is set up, just call `npx l-fen` to do it's magic.\n\n### Configuration\n\n```jsonc\n{\n  // the local folder containg all buckets and their files\n  \"storageDir\": \"./local-test-storage\",\n  // the port of the proxy, handling all HTTP requests and forwarding to the related HTTP Functions\n  \"httpProxyPort\": \"8080\",\n  // the port of the gRPC server handling stuff like Pub/Sub\n  \"grpcProxyPort\": \"50051\",\n  // the log level for l-fen. To print logs, created by Functions, set it to \"verbose\" or lower\n  \"loglevel\": \"verbose\",\n  // configuration for the Cloud Provider\n  \"provider\": {\n    // the Cloud Provider itself, right now, only GCP is supported\n    \"name\": \"GCP\",\n    // name of the project, used for some internal authentication processes\n    \"project\": \"my-test-project\"\n  },\n  // list of all Functions to be managed by l-fen\n  \"services\": [\n    {\n      // name of the Function, in case of HTTP Functions, it serves as the base route for the proxy\n      \"name\": \"http-handler\",\n      // path to the built main JS file\n      \"path\": \"./http-handler/index.js\",\n      // the exported entry point of the function\n      \"entryPoint\": \"handler\",\n      // the type of the function, \"HTTP\", \"PUBSUB\" or \"BUCKET\"\n      \"triggerType\": \"HTTP\",\n      // environment variables to be used by the Function\n      \"envVars\": {\n        \"PUBSUB_API_ENDPOINT\": \"localhost:50051\",\n        \"PROJECT_ID\": \"my-test-project\",\n        \"PUBSUB_TOPIC\": \"generic-topic\"\n      }\n    },\n    {\n      \"name\": \"file-writer\",\n      \"path\": \"./file-writer/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"PUBSUB\",\n      // if triggerType is either \"PUBSUB\" or \"BUCKET\", additional configuration has to be done here.\n      \"triggerConfig\": {\n        // the topic, the Pub/Sub function should subscribe\n        \"topic\": \"generic-topic\"\n      },\n      \"envVars\": {\n        \"MY_BUCKET\": \"my-bucket\"\n      }\n    },\n    {\n      \"name\": \"file-reader\",\n      \"path\": \"./file-reader/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"BUCKET\",\n      \"triggerConfig\": {\n        // file events the Function should listen on. Right now, only \"WRITE\" is supported\n        \"events\": [\"WRITE\"],\n        // the bucket the Function should listen on.\n        \"targetBucket\": \"my-bucket\"\n      },\n      \"envVars\": {}\n    }\n  ],\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n\n## Restrictions\n\n### Cloud Providers\n\nRight now, **l-fen** is only capable of handling Google Cloud Functions.  \nOther Cloud Providers are not yet supported.\n\n### Pub/Sub\n\nThe original idea was, to use **l-fen** out of the box, without the need of changing the Functions code to work with **l-fen**.\nUnfortunately, I didn't achieve that completely for Pub/Sub.\n\nTo allow **l-fen** to handle published event, the publisher has to use the gRPC server provided by **l-fen**.  \nWhen using the [Google Cloud Pub/Sub:Node.js Client](https://www.npmjs.com/package/@google-cloud/pubsub), the endpoint can be changed by instantiating the client with `new PubSub({apiEndpoint: 'localhost:50051'})`.\n\nI suggest to introduce an environment variable to the Function publishing events, to dynamically override the default endpoint and set that environment variable within the functions configuration in the `.fenrc.json`.\n\n## Under the hood\n\n**l-fen** consists of two main parts, the **orchestrator** and the **bootloader**, the brain and the heart of **l-fen**.\n\nThe orchestrator, the brain, plans the whole environment and knows what to do in certain cases and events.\nThe bootloader, the heart, is executing the Functions and providing all necessary data.\n\nThe orchestrator spawns new bootloader processes by executing the bootloader with required arguments.\n\n### The orchestrator\n\nThe orchestrator is responsible for loading the `.fenrc.json`, managing the Functions and setting up the local environment itself.\n\nDepending on the type of Functions, it adds a file watcher to the `storageDir` and calls the bootloader of the Functions that might be triggered by certain events in buckets.  \nThe same applies to HTTP and Pub/Sub Functions. The orchestrator provides a HTTP proxy and forwards any requests to the responsible Functions. It also starts the gRPC server that can be used by Pub/Sub to handle events and topics and call the according Functions.\n\n### The bootloader\n\nThe bootloader is responsible for starting a Function and providing all the required stuff the Function needs.\nIt's really nothing else like a small CLI tool, that expects certain arguments, like the payload it should forward to the Function or the environment variables the Function should be provided with.\n\nFor bucket and Pub/Sub Functions, it's quite straight forward. It `requires()` the Functions JS file, mapping the payload to the corresponding object and calling the `entryPoint` method with that object.\n\nIn case of an HTTP Function, the bootloader makes use of [express](https://www.npmjs.com/package/express) and [SuperTest](https://www.npmjs.com/package/supertest) create a proper request and handle stuff like different HTTP methods and headers.\n\nThe orchestrator is loading the `.fenrc.json`, managing all Functions, providing some basic setup like configuring a fake service account and file watcher on the specified `storageDir` and start the HTTP proxy.\n\nIt's also possible to start the bootloader without the orchestrator by running something like\n`npx l-fen-boot --event='{\\\"bucket\\\": \\\"user\\\", \\\"name\\\": \\\"111/b.json\\\"}' --path=\"../../../path/to/function/index.js\" --entryPoint=\"handleEvent\" --name=\"name-of-the-function\"`\n\n### Custom extension\n\nl-fen allows adding custom extensions that are executed for each loaded Function right before starting it.\n\nCreate your extension by exporting a class implementing the `LFenExtension` interface from `l-fen/types/extension.interface` and adding the absolute path to the config:\n\n```jsonc\n{\n  // all the other stuff, see above\n  ...\n  // here we add out extension\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n","readmeFilename":"README.md","gitHead":"53190fbdacf692c5aaf2fa0c9d6be11991714a00","_id":"l-fen@1.1.0-alpha.12","_nodeVersion":"18.19.0","_npmVersion":"9.6.3","dist":{"integrity":"sha512-WyyEcPOJkMJDCuiKNIHt71Vgsx34ynP4jwhEYX8BoLuqXCqcFiNZLVGMSHU6ACEoWLkHFG6dV+DUWFuBx1oPvA==","shasum":"fd484b173207b6998d835612dfa75f2ea61f6021","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.1.0-alpha.12.tgz","fileCount":102,"unpackedSize":1397888,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCICswtk+heSo2Z0QHMxgAFfUvycqajo12SMA6mGQwDBMWAiEA18mCvMn/f6HRJJO51lvTNC9ioi3AjIH/rXhI0WqshJw="}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.1.0-alpha.12_1705948492884_0.0902354406221142"},"_hasShrinkwrap":false},"1.1.0-alpha.13":{"name":"l-fen","version":"1.1.0-alpha.13","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"postinstall":"npx simple-git-hooks","test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.9.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_id":"l-fen@1.1.0-alpha.13","readme":"## Description\n\n**l-fen** is a small runtime to execute a bundle of Serverless Functions like they were running in the cloud.\n\n:warning: only [GCP](https://cloud.google.com/) supported right now\n\n## Motivation\n\nWhen working on backend services and applications built by a multitude of individual Serverless Functions with various triggers, like HTTP, Pub/Sub or Storage/Bucket-Events, I always struggled with testing the whole setup locally.  \nEven if there's a 100% test coverage for each function itself, one function of the whole application might expect a different event than another function publishes.\n\nSo my approach was always to test the code itself with unit and e2e tests, deploy to a test or dev environment and test the whole process of the application again. If something fails, I had to look into the logs, hoping that I logged enough to narrow down the problem and ideally get the line where my code broke or wasn't working as expected.\n\nAfter a while, I decided to write something like an emulator for the various triggers and events.  \nThe goal was to locally run my backend service, which had several HTTP functions writing files to buckets, triggering other functions, publishing events via Pub/Sub with other functions subscribing to these.\n\nIn the end, I came up with **l-fen**, the _local function environment_.\n\n## Usage\n\nFirst, you have to build your Functions, since **l-fen** `requires()` the JS file.\n\nWhen that's done, you can start to create your `.fenrc.json` file.  \nThis file contains the whole configuration of your setup and the individual Functions.\nIt's required to to tell **l-fen** which Functions to load and when to call these.\n\nWhen everything is set up, just call `npx l-fen` to do it's magic.\n\n### Configuration\n\n```jsonc\n{\n  // the local folder containg all buckets and their files\n  \"storageDir\": \"./local-test-storage\",\n  // the port of the proxy, handling all HTTP requests and forwarding to the related HTTP Functions\n  \"httpProxyPort\": \"8080\",\n  // the port of the gRPC server handling stuff like Pub/Sub\n  \"grpcProxyPort\": \"50051\",\n  // the log level for l-fen. To print logs, created by Functions, set it to \"verbose\" or lower\n  \"loglevel\": \"verbose\",\n  // configuration for the Cloud Provider\n  \"provider\": {\n    // the Cloud Provider itself, right now, only GCP is supported\n    \"name\": \"GCP\",\n    // name of the project, used for some internal authentication processes\n    \"project\": \"my-test-project\"\n  },\n  // list of all Functions to be managed by l-fen\n  \"services\": [\n    {\n      // name of the Function, in case of HTTP Functions, it serves as the base route for the proxy\n      \"name\": \"http-handler\",\n      // path to the built main JS file\n      \"path\": \"./http-handler/index.js\",\n      // the exported entry point of the function\n      \"entryPoint\": \"handler\",\n      // the type of the function, \"HTTP\", \"PUBSUB\" or \"BUCKET\"\n      \"triggerType\": \"HTTP\",\n      // environment variables to be used by the Function\n      \"envVars\": {\n        \"PUBSUB_API_ENDPOINT\": \"localhost:50051\",\n        \"PROJECT_ID\": \"my-test-project\",\n        \"PUBSUB_TOPIC\": \"generic-topic\"\n      }\n    },\n    {\n      \"name\": \"file-writer\",\n      \"path\": \"./file-writer/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"PUBSUB\",\n      // if triggerType is either \"PUBSUB\" or \"BUCKET\", additional configuration has to be done here.\n      \"triggerConfig\": {\n        // the topic, the Pub/Sub function should subscribe\n        \"topic\": \"generic-topic\"\n      },\n      \"envVars\": {\n        \"MY_BUCKET\": \"my-bucket\"\n      }\n    },\n    {\n      \"name\": \"file-reader\",\n      \"path\": \"./file-reader/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"BUCKET\",\n      \"triggerConfig\": {\n        // file events the Function should listen on. Right now, only \"WRITE\" is supported\n        \"events\": [\"WRITE\"],\n        // the bucket the Function should listen on.\n        \"targetBucket\": \"my-bucket\"\n      },\n      \"envVars\": {}\n    }\n  ],\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n\n## Restrictions\n\n### Cloud Providers\n\nRight now, **l-fen** is only capable of handling Google Cloud Functions.  \nOther Cloud Providers are not yet supported.\n\n### Pub/Sub\n\nThe original idea was, to use **l-fen** out of the box, without the need of changing the Functions code to work with **l-fen**.\nUnfortunately, I didn't achieve that completely for Pub/Sub.\n\nTo allow **l-fen** to handle published event, the publisher has to use the gRPC server provided by **l-fen**.  \nWhen using the [Google Cloud Pub/Sub:Node.js Client](https://www.npmjs.com/package/@google-cloud/pubsub), the endpoint can be changed by instantiating the client with `new PubSub({apiEndpoint: 'localhost:50051'})`.\n\nI suggest to introduce an environment variable to the Function publishing events, to dynamically override the default endpoint and set that environment variable within the functions configuration in the `.fenrc.json`.\n\n## Under the hood\n\n**l-fen** consists of two main parts, the **orchestrator** and the **bootloader**, the brain and the heart of **l-fen**.\n\nThe orchestrator, the brain, plans the whole environment and knows what to do in certain cases and events.\nThe bootloader, the heart, is executing the Functions and providing all necessary data.\n\nThe orchestrator spawns new bootloader processes by executing the bootloader with required arguments.\n\n### The orchestrator\n\nThe orchestrator is responsible for loading the `.fenrc.json`, managing the Functions and setting up the local environment itself.\n\nDepending on the type of Functions, it adds a file watcher to the `storageDir` and calls the bootloader of the Functions that might be triggered by certain events in buckets.  \nThe same applies to HTTP and Pub/Sub Functions. The orchestrator provides a HTTP proxy and forwards any requests to the responsible Functions. It also starts the gRPC server that can be used by Pub/Sub to handle events and topics and call the according Functions.\n\n### The bootloader\n\nThe bootloader is responsible for starting a Function and providing all the required stuff the Function needs.\nIt's really nothing else like a small CLI tool, that expects certain arguments, like the payload it should forward to the Function or the environment variables the Function should be provided with.\n\nFor bucket and Pub/Sub Functions, it's quite straight forward. It `requires()` the Functions JS file, mapping the payload to the corresponding object and calling the `entryPoint` method with that object.\n\nIn case of an HTTP Function, the bootloader makes use of [express](https://www.npmjs.com/package/express) and [SuperTest](https://www.npmjs.com/package/supertest) create a proper request and handle stuff like different HTTP methods and headers.\n\nThe orchestrator is loading the `.fenrc.json`, managing all Functions, providing some basic setup like configuring a fake service account and file watcher on the specified `storageDir` and start the HTTP proxy.\n\nIt's also possible to start the bootloader without the orchestrator by running something like\n`npx l-fen-boot --event='{\\\"bucket\\\": \\\"user\\\", \\\"name\\\": \\\"111/b.json\\\"}' --path=\"../../../path/to/function/index.js\" --entryPoint=\"handleEvent\" --name=\"name-of-the-function\"`\n\n### Custom extension\n\nl-fen allows adding custom extensions that are executed for each loaded Function right before starting it.\n\nCreate your extension by exporting a class implementing the `LFenExtension` interface from `l-fen/types/extension.interface` and adding the absolute path to the config:\n\n```jsonc\n{\n  // all the other stuff, see above\n  ...\n  // here we add out extension\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n","readmeFilename":"README.md","gitHead":"8d80eb0d0ef6c9834f997dd340342d8b504d58ae","types":"./lib/index.d.ts","_nodeVersion":"18.19.0","_npmVersion":"9.9.2","dist":{"integrity":"sha512-lk/0Oxhx2lwsZmu/3GmO74wUeSdM/+sM0HGEWp/qDRsyzxTPu+OhdGwE4J1xvPliW97lt99xlJefThKf4Ggnpw==","shasum":"976a996015f6e87ccb5506ee4c358fa20ea5db6e","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.1.0-alpha.13.tgz","fileCount":102,"unpackedSize":1397999,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDCold5L1zM/32pDvry1K6PRS65WTCw527uH0DlA152+QIgXyqkWGc6I8wY6K+KqeW/EuxNdzp05rHDX8hQGbIA/qg="}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.1.0-alpha.13_1705950084594_0.4375017618599397"},"_hasShrinkwrap":false},"1.1.0-alpha.14":{"name":"l-fen","version":"1.1.0-alpha.14","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"postinstall":"npx simple-git-hooks","test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.9.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","uuid":"^9.0.1","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_id":"l-fen@1.1.0-alpha.14","readme":"## Description\n\n**l-fen** is a small runtime to execute a bundle of Serverless Functions like they were running in the cloud.\n\n:warning: only [GCP](https://cloud.google.com/) supported right now\n\n## Motivation\n\nWhen working on backend services and applications built by a multitude of individual Serverless Functions with various triggers, like HTTP, Pub/Sub or Storage/Bucket-Events, I always struggled with testing the whole setup locally.  \nEven if there's a 100% test coverage for each function itself, one function of the whole application might expect a different event than another function publishes.\n\nSo my approach was always to test the code itself with unit and e2e tests, deploy to a test or dev environment and test the whole process of the application again. If something fails, I had to look into the logs, hoping that I logged enough to narrow down the problem and ideally get the line where my code broke or wasn't working as expected.\n\nAfter a while, I decided to write something like an emulator for the various triggers and events.  \nThe goal was to locally run my backend service, which had several HTTP functions writing files to buckets, triggering other functions, publishing events via Pub/Sub with other functions subscribing to these.\n\nIn the end, I came up with **l-fen**, the _local function environment_.\n\n## Usage\n\nFirst, you have to build your Functions, since **l-fen** `requires()` the JS file.\n\nWhen that's done, you can start to create your `.fenrc.json` file.  \nThis file contains the whole configuration of your setup and the individual Functions.\nIt's required to to tell **l-fen** which Functions to load and when to call these.\n\nWhen everything is set up, just call `npx l-fen` to do it's magic.\n\n### Configuration\n\n```jsonc\n{\n  // the local folder containg all buckets and their files\n  \"storageDir\": \"./local-test-storage\",\n  // the port of the proxy, handling all HTTP requests and forwarding to the related HTTP Functions\n  \"httpProxyPort\": \"8080\",\n  // the port of the gRPC server handling stuff like Pub/Sub\n  \"grpcProxyPort\": \"50051\",\n  // the log level for l-fen. To print logs, created by Functions, set it to \"verbose\" or lower\n  \"loglevel\": \"verbose\",\n  // configuration for the Cloud Provider\n  \"provider\": {\n    // the Cloud Provider itself, right now, only GCP is supported\n    \"name\": \"GCP\",\n    // name of the project, used for some internal authentication processes\n    \"project\": \"my-test-project\"\n  },\n  // list of all Functions to be managed by l-fen\n  \"services\": [\n    {\n      // name of the Function, in case of HTTP Functions, it serves as the base route for the proxy\n      \"name\": \"http-handler\",\n      // path to the built main JS file\n      \"path\": \"./http-handler/index.js\",\n      // the exported entry point of the function\n      \"entryPoint\": \"handler\",\n      // the type of the function, \"HTTP\", \"PUBSUB\" or \"BUCKET\"\n      \"triggerType\": \"HTTP\",\n      // environment variables to be used by the Function\n      \"envVars\": {\n        \"PUBSUB_API_ENDPOINT\": \"localhost:50051\",\n        \"PROJECT_ID\": \"my-test-project\",\n        \"PUBSUB_TOPIC\": \"generic-topic\"\n      }\n    },\n    {\n      \"name\": \"file-writer\",\n      \"path\": \"./file-writer/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"PUBSUB\",\n      // if triggerType is either \"PUBSUB\" or \"BUCKET\", additional configuration has to be done here.\n      \"triggerConfig\": {\n        // the topic, the Pub/Sub function should subscribe\n        \"topic\": \"generic-topic\"\n      },\n      \"envVars\": {\n        \"MY_BUCKET\": \"my-bucket\"\n      }\n    },\n    {\n      \"name\": \"file-reader\",\n      \"path\": \"./file-reader/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"BUCKET\",\n      \"triggerConfig\": {\n        // file events the Function should listen on. Right now, only \"WRITE\" is supported\n        \"events\": [\"WRITE\"],\n        // the bucket the Function should listen on.\n        \"targetBucket\": \"my-bucket\"\n      },\n      \"envVars\": {}\n    }\n  ],\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n\n## Restrictions\n\n### Cloud Providers\n\nRight now, **l-fen** is only capable of handling Google Cloud Functions.  \nOther Cloud Providers are not yet supported.\n\n### Pub/Sub\n\nThe original idea was, to use **l-fen** out of the box, without the need of changing the Functions code to work with **l-fen**.\nUnfortunately, I didn't achieve that completely for Pub/Sub.\n\nTo allow **l-fen** to handle published event, the publisher has to use the gRPC server provided by **l-fen**.  \nWhen using the [Google Cloud Pub/Sub:Node.js Client](https://www.npmjs.com/package/@google-cloud/pubsub), the endpoint can be changed by instantiating the client with `new PubSub({apiEndpoint: 'localhost:50051'})`.\n\nI suggest to introduce an environment variable to the Function publishing events, to dynamically override the default endpoint and set that environment variable within the functions configuration in the `.fenrc.json`.\n\n## Under the hood\n\n**l-fen** consists of two main parts, the **orchestrator** and the **bootloader**, the brain and the heart of **l-fen**.\n\nThe orchestrator, the brain, plans the whole environment and knows what to do in certain cases and events.\nThe bootloader, the heart, is executing the Functions and providing all necessary data.\n\nThe orchestrator spawns new bootloader processes by executing the bootloader with required arguments.\n\n### The orchestrator\n\nThe orchestrator is responsible for loading the `.fenrc.json`, managing the Functions and setting up the local environment itself.\n\nDepending on the type of Functions, it adds a file watcher to the `storageDir` and calls the bootloader of the Functions that might be triggered by certain events in buckets.  \nThe same applies to HTTP and Pub/Sub Functions. The orchestrator provides a HTTP proxy and forwards any requests to the responsible Functions. It also starts the gRPC server that can be used by Pub/Sub to handle events and topics and call the according Functions.\n\n### The bootloader\n\nThe bootloader is responsible for starting a Function and providing all the required stuff the Function needs.\nIt's really nothing else like a small CLI tool, that expects certain arguments, like the payload it should forward to the Function or the environment variables the Function should be provided with.\n\nFor bucket and Pub/Sub Functions, it's quite straight forward. It `requires()` the Functions JS file, mapping the payload to the corresponding object and calling the `entryPoint` method with that object.\n\nIn case of an HTTP Function, the bootloader makes use of [express](https://www.npmjs.com/package/express) and [SuperTest](https://www.npmjs.com/package/supertest) create a proper request and handle stuff like different HTTP methods and headers.\n\nThe orchestrator is loading the `.fenrc.json`, managing all Functions, providing some basic setup like configuring a fake service account and file watcher on the specified `storageDir` and start the HTTP proxy.\n\nIt's also possible to start the bootloader without the orchestrator by running something like\n`npx l-fen-boot --event='{\\\"bucket\\\": \\\"user\\\", \\\"name\\\": \\\"111/b.json\\\"}' --path=\"../../../path/to/function/index.js\" --entryPoint=\"handleEvent\" --name=\"name-of-the-function\"`\n\n### Custom extension\n\nl-fen allows adding custom extensions that are executed for each loaded Function right before starting it.\n\nCreate your extension by exporting a class implementing the `LFenExtension` interface from `l-fen/types/extension.interface` and adding the absolute path to the config:\n\n```jsonc\n{\n  // all the other stuff, see above\n  ...\n  // here we add out extension\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n","readmeFilename":"README.md","gitHead":"4340f413294d61109d7997f335b93f92ea40fb21","types":"./lib/index.d.ts","_nodeVersion":"18.19.0","_npmVersion":"9.9.2","dist":{"integrity":"sha512-RvzxfNnXUS/HcFVBeN3BwQGYjK/vtLErr9gSyyF7tKOc5bkGz9BgKHUuHGQHngMIbA9+VoExrocqXLOFTVF9yg==","shasum":"027167479b0b49295c69a3148aeb1ef2d78735db","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.1.0-alpha.14.tgz","fileCount":102,"unpackedSize":1399172,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDfbcLiY0LXSEx0LQ0o1g6skRkXAdggrQLMTo+kuz5uHQIhAJIlewTuw0QU2S82xgaINc00ZSOQ+G5Sjp8GIb0SdyrW"}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.1.0-alpha.14_1705952478127_0.6968839782938205"},"_hasShrinkwrap":false},"1.1.0-alpha.15":{"name":"l-fen","version":"1.1.0-alpha.15","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"postinstall":"npx simple-git-hooks","test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.9.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","uuid":"^9.0.1","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_id":"l-fen@1.1.0-alpha.15","readme":"## Description\n\n**l-fen** is a small runtime to execute a bundle of Serverless Functions like they were running in the cloud.\n\n:warning: only [GCP](https://cloud.google.com/) supported right now\n\n## Motivation\n\nWhen working on backend services and applications built by a multitude of individual Serverless Functions with various triggers, like HTTP, Pub/Sub or Storage/Bucket-Events, I always struggled with testing the whole setup locally.  \nEven if there's a 100% test coverage for each function itself, one function of the whole application might expect a different event than another function publishes.\n\nSo my approach was always to test the code itself with unit and e2e tests, deploy to a test or dev environment and test the whole process of the application again. If something fails, I had to look into the logs, hoping that I logged enough to narrow down the problem and ideally get the line where my code broke or wasn't working as expected.\n\nAfter a while, I decided to write something like an emulator for the various triggers and events.  \nThe goal was to locally run my backend service, which had several HTTP functions writing files to buckets, triggering other functions, publishing events via Pub/Sub with other functions subscribing to these.\n\nIn the end, I came up with **l-fen**, the _local function environment_.\n\n## Usage\n\nFirst, you have to build your Functions, since **l-fen** `requires()` the JS file.\n\nWhen that's done, you can start to create your `.fenrc.json` file.  \nThis file contains the whole configuration of your setup and the individual Functions.\nIt's required to to tell **l-fen** which Functions to load and when to call these.\n\nWhen everything is set up, just call `npx l-fen` to do it's magic.\n\n### Configuration\n\n```jsonc\n{\n  // the local folder containg all buckets and their files\n  \"storageDir\": \"./local-test-storage\",\n  // the port of the proxy, handling all HTTP requests and forwarding to the related HTTP Functions\n  \"httpProxyPort\": \"8080\",\n  // the port of the gRPC server handling stuff like Pub/Sub\n  \"grpcProxyPort\": \"50051\",\n  // the log level for l-fen. To print logs, created by Functions, set it to \"verbose\" or lower\n  \"loglevel\": \"verbose\",\n  // configuration for the Cloud Provider\n  \"provider\": {\n    // the Cloud Provider itself, right now, only GCP is supported\n    \"name\": \"GCP\",\n    // name of the project, used for some internal authentication processes\n    \"project\": \"my-test-project\"\n  },\n  // list of all Functions to be managed by l-fen\n  \"services\": [\n    {\n      // name of the Function, in case of HTTP Functions, it serves as the base route for the proxy\n      \"name\": \"http-handler\",\n      // path to the built main JS file\n      \"path\": \"./http-handler/index.js\",\n      // the exported entry point of the function\n      \"entryPoint\": \"handler\",\n      // the type of the function, \"HTTP\", \"PUBSUB\" or \"BUCKET\"\n      \"triggerType\": \"HTTP\",\n      // environment variables to be used by the Function\n      \"envVars\": {\n        \"PUBSUB_API_ENDPOINT\": \"localhost:50051\",\n        \"PROJECT_ID\": \"my-test-project\",\n        \"PUBSUB_TOPIC\": \"generic-topic\"\n      }\n    },\n    {\n      \"name\": \"file-writer\",\n      \"path\": \"./file-writer/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"PUBSUB\",\n      // if triggerType is either \"PUBSUB\" or \"BUCKET\", additional configuration has to be done here.\n      \"triggerConfig\": {\n        // the topic, the Pub/Sub function should subscribe\n        \"topic\": \"generic-topic\"\n      },\n      \"envVars\": {\n        \"MY_BUCKET\": \"my-bucket\"\n      }\n    },\n    {\n      \"name\": \"file-reader\",\n      \"path\": \"./file-reader/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"BUCKET\",\n      \"triggerConfig\": {\n        // file events the Function should listen on. Right now, only \"WRITE\" is supported\n        \"events\": [\"WRITE\"],\n        // the bucket the Function should listen on.\n        \"targetBucket\": \"my-bucket\"\n      },\n      \"envVars\": {}\n    }\n  ],\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n\n## Restrictions\n\n### Cloud Providers\n\nRight now, **l-fen** is only capable of handling Google Cloud Functions.  \nOther Cloud Providers are not yet supported.\n\n### Pub/Sub\n\nThe original idea was, to use **l-fen** out of the box, without the need of changing the Functions code to work with **l-fen**.\nUnfortunately, I didn't achieve that completely for Pub/Sub.\n\nTo allow **l-fen** to handle published event, the publisher has to use the gRPC server provided by **l-fen**.  \nWhen using the [Google Cloud Pub/Sub:Node.js Client](https://www.npmjs.com/package/@google-cloud/pubsub), the endpoint can be changed by instantiating the client with `new PubSub({apiEndpoint: 'localhost:50051'})`.\n\nI suggest to introduce an environment variable to the Function publishing events, to dynamically override the default endpoint and set that environment variable within the functions configuration in the `.fenrc.json`.\n\n## Under the hood\n\n**l-fen** consists of two main parts, the **orchestrator** and the **bootloader**, the brain and the heart of **l-fen**.\n\nThe orchestrator, the brain, plans the whole environment and knows what to do in certain cases and events.\nThe bootloader, the heart, is executing the Functions and providing all necessary data.\n\nThe orchestrator spawns new bootloader processes by executing the bootloader with required arguments.\n\n### The orchestrator\n\nThe orchestrator is responsible for loading the `.fenrc.json`, managing the Functions and setting up the local environment itself.\n\nDepending on the type of Functions, it adds a file watcher to the `storageDir` and calls the bootloader of the Functions that might be triggered by certain events in buckets.  \nThe same applies to HTTP and Pub/Sub Functions. The orchestrator provides a HTTP proxy and forwards any requests to the responsible Functions. It also starts the gRPC server that can be used by Pub/Sub to handle events and topics and call the according Functions.\n\n### The bootloader\n\nThe bootloader is responsible for starting a Function and providing all the required stuff the Function needs.\nIt's really nothing else like a small CLI tool, that expects certain arguments, like the payload it should forward to the Function or the environment variables the Function should be provided with.\n\nFor bucket and Pub/Sub Functions, it's quite straight forward. It `requires()` the Functions JS file, mapping the payload to the corresponding object and calling the `entryPoint` method with that object.\n\nIn case of an HTTP Function, the bootloader makes use of [express](https://www.npmjs.com/package/express) and [SuperTest](https://www.npmjs.com/package/supertest) create a proper request and handle stuff like different HTTP methods and headers.\n\nThe orchestrator is loading the `.fenrc.json`, managing all Functions, providing some basic setup like configuring a fake service account and file watcher on the specified `storageDir` and start the HTTP proxy.\n\nIt's also possible to start the bootloader without the orchestrator by running something like\n`npx l-fen-boot --event='{\\\"bucket\\\": \\\"user\\\", \\\"name\\\": \\\"111/b.json\\\"}' --path=\"../../../path/to/function/index.js\" --entryPoint=\"handleEvent\" --name=\"name-of-the-function\"`\n\n### Custom extension\n\nl-fen allows adding custom extensions that are executed for each loaded Function right before starting it.\n\nCreate your extension by exporting a class implementing the `LFenExtension` interface from `l-fen/types/extension.interface` and adding the absolute path to the config:\n\n```jsonc\n{\n  // all the other stuff, see above\n  ...\n  // here we add out extension\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n","readmeFilename":"README.md","gitHead":"f938fe4fad18f4981d8ebd6617cb88f5be8b4f3f","types":"./lib/index.d.ts","_nodeVersion":"18.19.0","_npmVersion":"9.9.2","dist":{"integrity":"sha512-aJ+FPfigS2cwSV2uGwdxORvHRv3mDjMdFyRKqbcKxy9B7cmUxZ8cT6aM4Wu7mN+fQDgYRNjP0BJOArw+N8XwHQ==","shasum":"ad57e8738f210be993544976fa06527d9c620ac1","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.1.0-alpha.15.tgz","fileCount":102,"unpackedSize":1400130,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIG0RcapALT/1gRk27AX1aV5BM2kkyo1Lseq9/g6foU+OAiEAw5oa0x//4zYkgMfDvx4yXgnLB5unJzGnpxL/DddGd7k="}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.1.0-alpha.15_1705953163430_0.17181839430325185"},"_hasShrinkwrap":false},"1.1.0-alpha.16":{"name":"l-fen","version":"1.1.0-alpha.16","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"postinstall":"npx simple-git-hooks","test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.9.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","uuid":"^9.0.1","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_id":"l-fen@1.1.0-alpha.16","readme":"## Description\n\n**l-fen** is a small runtime to execute a bundle of Serverless Functions like they were running in the cloud.\n\n:warning: only [GCP](https://cloud.google.com/) supported right now\n\n## Motivation\n\nWhen working on backend services and applications built by a multitude of individual Serverless Functions with various triggers, like HTTP, Pub/Sub or Storage/Bucket-Events, I always struggled with testing the whole setup locally.  \nEven if there's a 100% test coverage for each function itself, one function of the whole application might expect a different event than another function publishes.\n\nSo my approach was always to test the code itself with unit and e2e tests, deploy to a test or dev environment and test the whole process of the application again. If something fails, I had to look into the logs, hoping that I logged enough to narrow down the problem and ideally get the line where my code broke or wasn't working as expected.\n\nAfter a while, I decided to write something like an emulator for the various triggers and events.  \nThe goal was to locally run my backend service, which had several HTTP functions writing files to buckets, triggering other functions, publishing events via Pub/Sub with other functions subscribing to these.\n\nIn the end, I came up with **l-fen**, the _local function environment_.\n\n## Usage\n\nFirst, you have to build your Functions, since **l-fen** `requires()` the JS file.\n\nWhen that's done, you can start to create your `.fenrc.json` file.  \nThis file contains the whole configuration of your setup and the individual Functions.\nIt's required to to tell **l-fen** which Functions to load and when to call these.\n\nWhen everything is set up, just call `npx l-fen` to do it's magic.\n\n### Configuration\n\n```jsonc\n{\n  // the local folder containg all buckets and their files\n  \"storageDir\": \"./local-test-storage\",\n  // the port of the proxy, handling all HTTP requests and forwarding to the related HTTP Functions\n  \"httpProxyPort\": \"8080\",\n  // the port of the gRPC server handling stuff like Pub/Sub\n  \"grpcProxyPort\": \"50051\",\n  // the log level for l-fen. To print logs, created by Functions, set it to \"verbose\" or lower\n  \"loglevel\": \"verbose\",\n  // configuration for the Cloud Provider\n  \"provider\": {\n    // the Cloud Provider itself, right now, only GCP is supported\n    \"name\": \"GCP\",\n    // name of the project, used for some internal authentication processes\n    \"project\": \"my-test-project\"\n  },\n  // list of all Functions to be managed by l-fen\n  \"services\": [\n    {\n      // name of the Function, in case of HTTP Functions, it serves as the base route for the proxy\n      \"name\": \"http-handler\",\n      // path to the built main JS file\n      \"path\": \"./http-handler/index.js\",\n      // the exported entry point of the function\n      \"entryPoint\": \"handler\",\n      // the type of the function, \"HTTP\", \"PUBSUB\" or \"BUCKET\"\n      \"triggerType\": \"HTTP\",\n      // environment variables to be used by the Function\n      \"envVars\": {\n        \"PUBSUB_API_ENDPOINT\": \"localhost:50051\",\n        \"PROJECT_ID\": \"my-test-project\",\n        \"PUBSUB_TOPIC\": \"generic-topic\"\n      }\n    },\n    {\n      \"name\": \"file-writer\",\n      \"path\": \"./file-writer/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"PUBSUB\",\n      // if triggerType is either \"PUBSUB\" or \"BUCKET\", additional configuration has to be done here.\n      \"triggerConfig\": {\n        // the topic, the Pub/Sub function should subscribe\n        \"topic\": \"generic-topic\"\n      },\n      \"envVars\": {\n        \"MY_BUCKET\": \"my-bucket\"\n      }\n    },\n    {\n      \"name\": \"file-reader\",\n      \"path\": \"./file-reader/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"BUCKET\",\n      \"triggerConfig\": {\n        // file events the Function should listen on. Right now, only \"WRITE\" is supported\n        \"events\": [\"WRITE\"],\n        // the bucket the Function should listen on.\n        \"targetBucket\": \"my-bucket\"\n      },\n      \"envVars\": {}\n    }\n  ],\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n\n## Restrictions\n\n### Cloud Providers\n\nRight now, **l-fen** is only capable of handling Google Cloud Functions.  \nOther Cloud Providers are not yet supported.\n\n### Pub/Sub\n\nThe original idea was, to use **l-fen** out of the box, without the need of changing the Functions code to work with **l-fen**.\nUnfortunately, I didn't achieve that completely for Pub/Sub.\n\nTo allow **l-fen** to handle published event, the publisher has to use the gRPC server provided by **l-fen**.  \nWhen using the [Google Cloud Pub/Sub:Node.js Client](https://www.npmjs.com/package/@google-cloud/pubsub), the endpoint can be changed by instantiating the client with `new PubSub({apiEndpoint: 'localhost:50051'})`.\n\nI suggest to introduce an environment variable to the Function publishing events, to dynamically override the default endpoint and set that environment variable within the functions configuration in the `.fenrc.json`.\n\n## Under the hood\n\n**l-fen** consists of two main parts, the **orchestrator** and the **bootloader**, the brain and the heart of **l-fen**.\n\nThe orchestrator, the brain, plans the whole environment and knows what to do in certain cases and events.\nThe bootloader, the heart, is executing the Functions and providing all necessary data.\n\nThe orchestrator spawns new bootloader processes by executing the bootloader with required arguments.\n\n### The orchestrator\n\nThe orchestrator is responsible for loading the `.fenrc.json`, managing the Functions and setting up the local environment itself.\n\nDepending on the type of Functions, it adds a file watcher to the `storageDir` and calls the bootloader of the Functions that might be triggered by certain events in buckets.  \nThe same applies to HTTP and Pub/Sub Functions. The orchestrator provides a HTTP proxy and forwards any requests to the responsible Functions. It also starts the gRPC server that can be used by Pub/Sub to handle events and topics and call the according Functions.\n\n### The bootloader\n\nThe bootloader is responsible for starting a Function and providing all the required stuff the Function needs.\nIt's really nothing else like a small CLI tool, that expects certain arguments, like the payload it should forward to the Function or the environment variables the Function should be provided with.\n\nFor bucket and Pub/Sub Functions, it's quite straight forward. It `requires()` the Functions JS file, mapping the payload to the corresponding object and calling the `entryPoint` method with that object.\n\nIn case of an HTTP Function, the bootloader makes use of [express](https://www.npmjs.com/package/express) and [SuperTest](https://www.npmjs.com/package/supertest) create a proper request and handle stuff like different HTTP methods and headers.\n\nThe orchestrator is loading the `.fenrc.json`, managing all Functions, providing some basic setup like configuring a fake service account and file watcher on the specified `storageDir` and start the HTTP proxy.\n\nIt's also possible to start the bootloader without the orchestrator by running something like\n`npx l-fen-boot --event='{\\\"bucket\\\": \\\"user\\\", \\\"name\\\": \\\"111/b.json\\\"}' --path=\"../../../path/to/function/index.js\" --entryPoint=\"handleEvent\" --name=\"name-of-the-function\"`\n\n### Custom extension\n\nl-fen allows adding custom extensions that are executed for each loaded Function right before starting it.\n\nCreate your extension by exporting a class implementing the `LFenExtension` interface from `l-fen/types/extension.interface` and adding the absolute path to the config:\n\n```jsonc\n{\n  // all the other stuff, see above\n  ...\n  // here we add out extension\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n","readmeFilename":"README.md","gitHead":"df2c20d5cd573a14f433f8d6ac1d33f34347407d","types":"./lib/index.d.ts","_nodeVersion":"18.19.0","_npmVersion":"9.9.2","dist":{"integrity":"sha512-MKbUwbWIdCQkEhCpbA9hPAJCWIILnNnH9uACOptTyxHrT1m2/Svhb1JDse+hi3EP/LeiA2hNvcCr2FZaqCeIBg==","shasum":"83cff57efa04341d3f9527eac30792b4cd9a3cad","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.1.0-alpha.16.tgz","fileCount":102,"unpackedSize":1400408,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQC1lk22TOP7S9jaDN/NINqZCowVOrcPLMMDB0u2Gub/RAIgV3BPGBeddsxm4RxGeyEt0i0+OKI2fNy0nL7D1/QD/zM="}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.1.0-alpha.16_1705953548251_0.6376923460330739"},"_hasShrinkwrap":false},"1.1.0-alpha.17":{"name":"l-fen","version":"1.1.0-alpha.17","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"postinstall":"npx simple-git-hooks","test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.9.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","uuid":"^9.0.1","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_id":"l-fen@1.1.0-alpha.17","readme":"## Description\n\n**l-fen** is a small runtime to execute a bundle of Serverless Functions like they were running in the cloud.\n\n:warning: only [GCP](https://cloud.google.com/) supported right now\n\n## Motivation\n\nWhen working on backend services and applications built by a multitude of individual Serverless Functions with various triggers, like HTTP, Pub/Sub or Storage/Bucket-Events, I always struggled with testing the whole setup locally.  \nEven if there's a 100% test coverage for each function itself, one function of the whole application might expect a different event than another function publishes.\n\nSo my approach was always to test the code itself with unit and e2e tests, deploy to a test or dev environment and test the whole process of the application again. If something fails, I had to look into the logs, hoping that I logged enough to narrow down the problem and ideally get the line where my code broke or wasn't working as expected.\n\nAfter a while, I decided to write something like an emulator for the various triggers and events.  \nThe goal was to locally run my backend service, which had several HTTP functions writing files to buckets, triggering other functions, publishing events via Pub/Sub with other functions subscribing to these.\n\nIn the end, I came up with **l-fen**, the _local function environment_.\n\n## Usage\n\nFirst, you have to build your Functions, since **l-fen** `requires()` the JS file.\n\nWhen that's done, you can start to create your `.fenrc.json` file.  \nThis file contains the whole configuration of your setup and the individual Functions.\nIt's required to to tell **l-fen** which Functions to load and when to call these.\n\nWhen everything is set up, just call `npx l-fen` to do it's magic.\n\n### Configuration\n\n```jsonc\n{\n  // the local folder containg all buckets and their files\n  \"storageDir\": \"./local-test-storage\",\n  // the port of the proxy, handling all HTTP requests and forwarding to the related HTTP Functions\n  \"httpProxyPort\": \"8080\",\n  // the port of the gRPC server handling stuff like Pub/Sub\n  \"grpcProxyPort\": \"50051\",\n  // the log level for l-fen. To print logs, created by Functions, set it to \"verbose\" or lower\n  \"loglevel\": \"verbose\",\n  // configuration for the Cloud Provider\n  \"provider\": {\n    // the Cloud Provider itself, right now, only GCP is supported\n    \"name\": \"GCP\",\n    // name of the project, used for some internal authentication processes\n    \"project\": \"my-test-project\"\n  },\n  // list of all Functions to be managed by l-fen\n  \"services\": [\n    {\n      // name of the Function, in case of HTTP Functions, it serves as the base route for the proxy\n      \"name\": \"http-handler\",\n      // path to the built main JS file\n      \"path\": \"./http-handler/index.js\",\n      // the exported entry point of the function\n      \"entryPoint\": \"handler\",\n      // the type of the function, \"HTTP\", \"PUBSUB\" or \"BUCKET\"\n      \"triggerType\": \"HTTP\",\n      // environment variables to be used by the Function\n      \"envVars\": {\n        \"PUBSUB_API_ENDPOINT\": \"localhost:50051\",\n        \"PROJECT_ID\": \"my-test-project\",\n        \"PUBSUB_TOPIC\": \"generic-topic\"\n      }\n    },\n    {\n      \"name\": \"file-writer\",\n      \"path\": \"./file-writer/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"PUBSUB\",\n      // if triggerType is either \"PUBSUB\" or \"BUCKET\", additional configuration has to be done here.\n      \"triggerConfig\": {\n        // the topic, the Pub/Sub function should subscribe\n        \"topic\": \"generic-topic\"\n      },\n      \"envVars\": {\n        \"MY_BUCKET\": \"my-bucket\"\n      }\n    },\n    {\n      \"name\": \"file-reader\",\n      \"path\": \"./file-reader/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"BUCKET\",\n      \"triggerConfig\": {\n        // file events the Function should listen on. Right now, only \"WRITE\" is supported\n        \"events\": [\"WRITE\"],\n        // the bucket the Function should listen on.\n        \"targetBucket\": \"my-bucket\"\n      },\n      \"envVars\": {}\n    }\n  ],\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n\n## Restrictions\n\n### Cloud Providers\n\nRight now, **l-fen** is only capable of handling Google Cloud Functions.  \nOther Cloud Providers are not yet supported.\n\n### Pub/Sub\n\nThe original idea was, to use **l-fen** out of the box, without the need of changing the Functions code to work with **l-fen**.\nUnfortunately, I didn't achieve that completely for Pub/Sub.\n\nTo allow **l-fen** to handle published event, the publisher has to use the gRPC server provided by **l-fen**.  \nWhen using the [Google Cloud Pub/Sub:Node.js Client](https://www.npmjs.com/package/@google-cloud/pubsub), the endpoint can be changed by instantiating the client with `new PubSub({apiEndpoint: 'localhost:50051'})`.\n\nI suggest to introduce an environment variable to the Function publishing events, to dynamically override the default endpoint and set that environment variable within the functions configuration in the `.fenrc.json`.\n\n## Under the hood\n\n**l-fen** consists of two main parts, the **orchestrator** and the **bootloader**, the brain and the heart of **l-fen**.\n\nThe orchestrator, the brain, plans the whole environment and knows what to do in certain cases and events.\nThe bootloader, the heart, is executing the Functions and providing all necessary data.\n\nThe orchestrator spawns new bootloader processes by executing the bootloader with required arguments.\n\n### The orchestrator\n\nThe orchestrator is responsible for loading the `.fenrc.json`, managing the Functions and setting up the local environment itself.\n\nDepending on the type of Functions, it adds a file watcher to the `storageDir` and calls the bootloader of the Functions that might be triggered by certain events in buckets.  \nThe same applies to HTTP and Pub/Sub Functions. The orchestrator provides a HTTP proxy and forwards any requests to the responsible Functions. It also starts the gRPC server that can be used by Pub/Sub to handle events and topics and call the according Functions.\n\n### The bootloader\n\nThe bootloader is responsible for starting a Function and providing all the required stuff the Function needs.\nIt's really nothing else like a small CLI tool, that expects certain arguments, like the payload it should forward to the Function or the environment variables the Function should be provided with.\n\nFor bucket and Pub/Sub Functions, it's quite straight forward. It `requires()` the Functions JS file, mapping the payload to the corresponding object and calling the `entryPoint` method with that object.\n\nIn case of an HTTP Function, the bootloader makes use of [express](https://www.npmjs.com/package/express) and [SuperTest](https://www.npmjs.com/package/supertest) create a proper request and handle stuff like different HTTP methods and headers.\n\nThe orchestrator is loading the `.fenrc.json`, managing all Functions, providing some basic setup like configuring a fake service account and file watcher on the specified `storageDir` and start the HTTP proxy.\n\nIt's also possible to start the bootloader without the orchestrator by running something like\n`npx l-fen-boot --event='{\\\"bucket\\\": \\\"user\\\", \\\"name\\\": \\\"111/b.json\\\"}' --path=\"../../../path/to/function/index.js\" --entryPoint=\"handleEvent\" --name=\"name-of-the-function\"`\n\n### Custom extension\n\nl-fen allows adding custom extensions that are executed for each loaded Function right before starting it.\n\nCreate your extension by exporting a class implementing the `LFenExtension` interface from `l-fen/types/extension.interface` and adding the absolute path to the config:\n\n```jsonc\n{\n  // all the other stuff, see above\n  ...\n  // here we add out extension\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n","readmeFilename":"README.md","gitHead":"704bde34e7e14ab6335d6d7796882df2628f92f7","types":"./lib/index.d.ts","_nodeVersion":"18.19.0","_npmVersion":"9.9.2","dist":{"integrity":"sha512-D5sKUecjKmGONqZ4wF4jleTOtn9A3j9B7d/zsQX84mLZG0SL+424mg91nmOXGu8/hUVSuoqbdB3i+aBu6lxibw==","shasum":"c9eb3bfca7b860214d167e029ff766ef84e0defe","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.1.0-alpha.17.tgz","fileCount":102,"unpackedSize":1400726,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDhFGjgfThTda6I4vJJB5KDCnIJAow2/e6K/2LaOveKDwIhAOfA414+ZjyF2h+uPpSHZokpb7Q+UBiSoJegccZfuJtP"}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.1.0-alpha.17_1705955065539_0.11533624427995726"},"_hasShrinkwrap":false},"1.1.0-alpha.18":{"name":"l-fen","version":"1.1.0-alpha.18","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"postinstall":"npx simple-git-hooks","test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.9.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","uuid":"^9.0.1","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_id":"l-fen@1.1.0-alpha.18","readme":"## Description\n\n**l-fen** is a small runtime to execute a bundle of Serverless Functions like they were running in the cloud.\n\n:warning: only [GCP](https://cloud.google.com/) supported right now\n\n## Motivation\n\nWhen working on backend services and applications built by a multitude of individual Serverless Functions with various triggers, like HTTP, Pub/Sub or Storage/Bucket-Events, I always struggled with testing the whole setup locally.  \nEven if there's a 100% test coverage for each function itself, one function of the whole application might expect a different event than another function publishes.\n\nSo my approach was always to test the code itself with unit and e2e tests, deploy to a test or dev environment and test the whole process of the application again. If something fails, I had to look into the logs, hoping that I logged enough to narrow down the problem and ideally get the line where my code broke or wasn't working as expected.\n\nAfter a while, I decided to write something like an emulator for the various triggers and events.  \nThe goal was to locally run my backend service, which had several HTTP functions writing files to buckets, triggering other functions, publishing events via Pub/Sub with other functions subscribing to these.\n\nIn the end, I came up with **l-fen**, the _local function environment_.\n\n## Usage\n\nFirst, you have to build your Functions, since **l-fen** `requires()` the JS file.\n\nWhen that's done, you can start to create your `.fenrc.json` file.  \nThis file contains the whole configuration of your setup and the individual Functions.\nIt's required to to tell **l-fen** which Functions to load and when to call these.\n\nWhen everything is set up, just call `npx l-fen` to do it's magic.\n\n### Configuration\n\n```jsonc\n{\n  // the local folder containg all buckets and their files\n  \"storageDir\": \"./local-test-storage\",\n  // the port of the proxy, handling all HTTP requests and forwarding to the related HTTP Functions\n  \"httpProxyPort\": \"8080\",\n  // the port of the gRPC server handling stuff like Pub/Sub\n  \"grpcProxyPort\": \"50051\",\n  // the log level for l-fen. To print logs, created by Functions, set it to \"verbose\" or lower\n  \"loglevel\": \"verbose\",\n  // configuration for the Cloud Provider\n  \"provider\": {\n    // the Cloud Provider itself, right now, only GCP is supported\n    \"name\": \"GCP\",\n    // name of the project, used for some internal authentication processes\n    \"project\": \"my-test-project\"\n  },\n  // list of all Functions to be managed by l-fen\n  \"services\": [\n    {\n      // name of the Function, in case of HTTP Functions, it serves as the base route for the proxy\n      \"name\": \"http-handler\",\n      // path to the built main JS file\n      \"path\": \"./http-handler/index.js\",\n      // the exported entry point of the function\n      \"entryPoint\": \"handler\",\n      // the type of the function, \"HTTP\", \"PUBSUB\" or \"BUCKET\"\n      \"triggerType\": \"HTTP\",\n      // environment variables to be used by the Function\n      \"envVars\": {\n        \"PUBSUB_API_ENDPOINT\": \"localhost:50051\",\n        \"PROJECT_ID\": \"my-test-project\",\n        \"PUBSUB_TOPIC\": \"generic-topic\"\n      }\n    },\n    {\n      \"name\": \"file-writer\",\n      \"path\": \"./file-writer/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"PUBSUB\",\n      // if triggerType is either \"PUBSUB\" or \"BUCKET\", additional configuration has to be done here.\n      \"triggerConfig\": {\n        // the topic, the Pub/Sub function should subscribe\n        \"topic\": \"generic-topic\"\n      },\n      \"envVars\": {\n        \"MY_BUCKET\": \"my-bucket\"\n      }\n    },\n    {\n      \"name\": \"file-reader\",\n      \"path\": \"./file-reader/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"BUCKET\",\n      \"triggerConfig\": {\n        // file events the Function should listen on. Right now, only \"WRITE\" is supported\n        \"events\": [\"WRITE\"],\n        // the bucket the Function should listen on.\n        \"targetBucket\": \"my-bucket\"\n      },\n      \"envVars\": {}\n    }\n  ],\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n\n## Restrictions\n\n### Cloud Providers\n\nRight now, **l-fen** is only capable of handling Google Cloud Functions.  \nOther Cloud Providers are not yet supported.\n\n### Pub/Sub\n\nThe original idea was, to use **l-fen** out of the box, without the need of changing the Functions code to work with **l-fen**.\nUnfortunately, I didn't achieve that completely for Pub/Sub.\n\nTo allow **l-fen** to handle published event, the publisher has to use the gRPC server provided by **l-fen**.  \nWhen using the [Google Cloud Pub/Sub:Node.js Client](https://www.npmjs.com/package/@google-cloud/pubsub), the endpoint can be changed by instantiating the client with `new PubSub({apiEndpoint: 'localhost:50051'})`.\n\nI suggest to introduce an environment variable to the Function publishing events, to dynamically override the default endpoint and set that environment variable within the functions configuration in the `.fenrc.json`.\n\n## Under the hood\n\n**l-fen** consists of two main parts, the **orchestrator** and the **bootloader**, the brain and the heart of **l-fen**.\n\nThe orchestrator, the brain, plans the whole environment and knows what to do in certain cases and events.\nThe bootloader, the heart, is executing the Functions and providing all necessary data.\n\nThe orchestrator spawns new bootloader processes by executing the bootloader with required arguments.\n\n### The orchestrator\n\nThe orchestrator is responsible for loading the `.fenrc.json`, managing the Functions and setting up the local environment itself.\n\nDepending on the type of Functions, it adds a file watcher to the `storageDir` and calls the bootloader of the Functions that might be triggered by certain events in buckets.  \nThe same applies to HTTP and Pub/Sub Functions. The orchestrator provides a HTTP proxy and forwards any requests to the responsible Functions. It also starts the gRPC server that can be used by Pub/Sub to handle events and topics and call the according Functions.\n\n### The bootloader\n\nThe bootloader is responsible for starting a Function and providing all the required stuff the Function needs.\nIt's really nothing else like a small CLI tool, that expects certain arguments, like the payload it should forward to the Function or the environment variables the Function should be provided with.\n\nFor bucket and Pub/Sub Functions, it's quite straight forward. It `requires()` the Functions JS file, mapping the payload to the corresponding object and calling the `entryPoint` method with that object.\n\nIn case of an HTTP Function, the bootloader makes use of [express](https://www.npmjs.com/package/express) and [SuperTest](https://www.npmjs.com/package/supertest) create a proper request and handle stuff like different HTTP methods and headers.\n\nThe orchestrator is loading the `.fenrc.json`, managing all Functions, providing some basic setup like configuring a fake service account and file watcher on the specified `storageDir` and start the HTTP proxy.\n\nIt's also possible to start the bootloader without the orchestrator by running something like\n`npx l-fen-boot --event='{\\\"bucket\\\": \\\"user\\\", \\\"name\\\": \\\"111/b.json\\\"}' --path=\"../../../path/to/function/index.js\" --entryPoint=\"handleEvent\" --name=\"name-of-the-function\"`\n\n### Custom extension\n\nl-fen allows adding custom extensions that are executed for each loaded Function right before starting it.\n\nCreate your extension by exporting a class implementing the `LFenExtension` interface from `l-fen/types/extension.interface` and adding the absolute path to the config:\n\n```jsonc\n{\n  // all the other stuff, see above\n  ...\n  // here we add out extension\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n","readmeFilename":"README.md","gitHead":"c2952a7576de1656f9ea1a81172815f7fc946b6a","types":"./lib/index.d.ts","_nodeVersion":"18.19.0","_npmVersion":"9.9.2","dist":{"integrity":"sha512-WC+Yf/LztrXW0Nfj/m0pIdKWTpWkuePM8Uf6xWYG49I3CrYED+lFbyi2+G3AQNXKThFwWqT1yUS+locBYa54aA==","shasum":"7f1ce22871f0ccfd94ad935c09b8380105d503b2","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.1.0-alpha.18.tgz","fileCount":102,"unpackedSize":1401733,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIEaeHmXEgRX34IYWVejFI9yVEQoK8s6ZiNvk9N/zpzbiAiEA+s+QR/H7jsY1cwDMp54yDPrGNlE+/uR075iDSSRSoRQ="}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.1.0-alpha.18_1705955881762_0.10231955601206577"},"_hasShrinkwrap":false},"1.1.0-alpha.19":{"name":"l-fen","version":"1.1.0-alpha.19","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"postinstall":"npx simple-git-hooks","test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.9.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","uuid":"^9.0.1","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_id":"l-fen@1.1.0-alpha.19","readme":"## Description\n\n**l-fen** is a small runtime to execute a bundle of Serverless Functions like they were running in the cloud.\n\n:warning: only [GCP](https://cloud.google.com/) supported right now\n\n## Motivation\n\nWhen working on backend services and applications built by a multitude of individual Serverless Functions with various triggers, like HTTP, Pub/Sub or Storage/Bucket-Events, I always struggled with testing the whole setup locally.  \nEven if there's a 100% test coverage for each function itself, one function of the whole application might expect a different event than another function publishes.\n\nSo my approach was always to test the code itself with unit and e2e tests, deploy to a test or dev environment and test the whole process of the application again. If something fails, I had to look into the logs, hoping that I logged enough to narrow down the problem and ideally get the line where my code broke or wasn't working as expected.\n\nAfter a while, I decided to write something like an emulator for the various triggers and events.  \nThe goal was to locally run my backend service, which had several HTTP functions writing files to buckets, triggering other functions, publishing events via Pub/Sub with other functions subscribing to these.\n\nIn the end, I came up with **l-fen**, the _local function environment_.\n\n## Usage\n\nFirst, you have to build your Functions, since **l-fen** `requires()` the JS file.\n\nWhen that's done, you can start to create your `.fenrc.json` file.  \nThis file contains the whole configuration of your setup and the individual Functions.\nIt's required to to tell **l-fen** which Functions to load and when to call these.\n\nWhen everything is set up, just call `npx l-fen` to do it's magic.\n\n### Configuration\n\n```jsonc\n{\n  // the local folder containg all buckets and their files\n  \"storageDir\": \"./local-test-storage\",\n  // the port of the proxy, handling all HTTP requests and forwarding to the related HTTP Functions\n  \"httpProxyPort\": \"8080\",\n  // the port of the gRPC server handling stuff like Pub/Sub\n  \"grpcProxyPort\": \"50051\",\n  // the log level for l-fen. To print logs, created by Functions, set it to \"verbose\" or lower\n  \"loglevel\": \"verbose\",\n  // configuration for the Cloud Provider\n  \"provider\": {\n    // the Cloud Provider itself, right now, only GCP is supported\n    \"name\": \"GCP\",\n    // name of the project, used for some internal authentication processes\n    \"project\": \"my-test-project\"\n  },\n  // list of all Functions to be managed by l-fen\n  \"services\": [\n    {\n      // name of the Function, in case of HTTP Functions, it serves as the base route for the proxy\n      \"name\": \"http-handler\",\n      // path to the built main JS file\n      \"path\": \"./http-handler/index.js\",\n      // the exported entry point of the function\n      \"entryPoint\": \"handler\",\n      // the type of the function, \"HTTP\", \"PUBSUB\" or \"BUCKET\"\n      \"triggerType\": \"HTTP\",\n      // environment variables to be used by the Function\n      \"envVars\": {\n        \"PUBSUB_API_ENDPOINT\": \"localhost:50051\",\n        \"PROJECT_ID\": \"my-test-project\",\n        \"PUBSUB_TOPIC\": \"generic-topic\"\n      }\n    },\n    {\n      \"name\": \"file-writer\",\n      \"path\": \"./file-writer/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"PUBSUB\",\n      // if triggerType is either \"PUBSUB\" or \"BUCKET\", additional configuration has to be done here.\n      \"triggerConfig\": {\n        // the topic, the Pub/Sub function should subscribe\n        \"topic\": \"generic-topic\"\n      },\n      \"envVars\": {\n        \"MY_BUCKET\": \"my-bucket\"\n      }\n    },\n    {\n      \"name\": \"file-reader\",\n      \"path\": \"./file-reader/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"BUCKET\",\n      \"triggerConfig\": {\n        // file events the Function should listen on. Right now, only \"WRITE\" is supported\n        \"events\": [\"WRITE\"],\n        // the bucket the Function should listen on.\n        \"targetBucket\": \"my-bucket\"\n      },\n      \"envVars\": {}\n    }\n  ],\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n\n## Restrictions\n\n### Cloud Providers\n\nRight now, **l-fen** is only capable of handling Google Cloud Functions.  \nOther Cloud Providers are not yet supported.\n\n### Pub/Sub\n\nThe original idea was, to use **l-fen** out of the box, without the need of changing the Functions code to work with **l-fen**.\nUnfortunately, I didn't achieve that completely for Pub/Sub.\n\nTo allow **l-fen** to handle published event, the publisher has to use the gRPC server provided by **l-fen**.  \nWhen using the [Google Cloud Pub/Sub:Node.js Client](https://www.npmjs.com/package/@google-cloud/pubsub), the endpoint can be changed by instantiating the client with `new PubSub({apiEndpoint: 'localhost:50051'})`.\n\nI suggest to introduce an environment variable to the Function publishing events, to dynamically override the default endpoint and set that environment variable within the functions configuration in the `.fenrc.json`.\n\n## Under the hood\n\n**l-fen** consists of two main parts, the **orchestrator** and the **bootloader**, the brain and the heart of **l-fen**.\n\nThe orchestrator, the brain, plans the whole environment and knows what to do in certain cases and events.\nThe bootloader, the heart, is executing the Functions and providing all necessary data.\n\nThe orchestrator spawns new bootloader processes by executing the bootloader with required arguments.\n\n### The orchestrator\n\nThe orchestrator is responsible for loading the `.fenrc.json`, managing the Functions and setting up the local environment itself.\n\nDepending on the type of Functions, it adds a file watcher to the `storageDir` and calls the bootloader of the Functions that might be triggered by certain events in buckets.  \nThe same applies to HTTP and Pub/Sub Functions. The orchestrator provides a HTTP proxy and forwards any requests to the responsible Functions. It also starts the gRPC server that can be used by Pub/Sub to handle events and topics and call the according Functions.\n\n### The bootloader\n\nThe bootloader is responsible for starting a Function and providing all the required stuff the Function needs.\nIt's really nothing else like a small CLI tool, that expects certain arguments, like the payload it should forward to the Function or the environment variables the Function should be provided with.\n\nFor bucket and Pub/Sub Functions, it's quite straight forward. It `requires()` the Functions JS file, mapping the payload to the corresponding object and calling the `entryPoint` method with that object.\n\nIn case of an HTTP Function, the bootloader makes use of [express](https://www.npmjs.com/package/express) and [SuperTest](https://www.npmjs.com/package/supertest) create a proper request and handle stuff like different HTTP methods and headers.\n\nThe orchestrator is loading the `.fenrc.json`, managing all Functions, providing some basic setup like configuring a fake service account and file watcher on the specified `storageDir` and start the HTTP proxy.\n\nIt's also possible to start the bootloader without the orchestrator by running something like\n`npx l-fen-boot --event='{\\\"bucket\\\": \\\"user\\\", \\\"name\\\": \\\"111/b.json\\\"}' --path=\"../../../path/to/function/index.js\" --entryPoint=\"handleEvent\" --name=\"name-of-the-function\"`\n\n### Custom extension\n\nl-fen allows adding custom extensions that are executed for each loaded Function right before starting it.\n\nCreate your extension by exporting a class implementing the `LFenExtension` interface from `l-fen/types/extension.interface` and adding the absolute path to the config:\n\n```jsonc\n{\n  // all the other stuff, see above\n  ...\n  // here we add out extension\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n","readmeFilename":"README.md","gitHead":"c6168d2df6238093e55390ab3b50c76ccb03fd63","types":"./lib/index.d.ts","_nodeVersion":"18.19.0","_npmVersion":"9.9.2","dist":{"integrity":"sha512-3NRx8yCSY5DypHqHZFbH5Yn27dV58LQQf/38fm1zOGZvK338F+wlQPdrk8KFgch/4ru4EiaXKuTupjkHZxNefg==","shasum":"95099818a7d1e6ad1b498d22c8d4c4afe6e90cbe","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.1.0-alpha.19.tgz","fileCount":102,"unpackedSize":1401251,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIEC87Qg4qwMYDMtbmTxfWjZzW6BR/yRJsVCVKKprTlkaAiEA+3G7cQnwOwl/jqsxRuzL70hfL6eA6gb2MPwbWF3KfcQ="}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.1.0-alpha.19_1705996648800_0.38028921246626446"},"_hasShrinkwrap":false},"1.1.0":{"name":"l-fen","version":"1.1.0","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"postinstall":"npx simple-git-hooks","test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.9.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","uuid":"^9.0.1","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_id":"l-fen@1.1.0","gitHead":"bad9e818a95b359fa8aa1102f394d698ae104b23","types":"./lib/index.d.ts","_nodeVersion":"18.19.0","_npmVersion":"9.9.2","dist":{"integrity":"sha512-97HODSyP30spfEcpWpopFZjRuVFjVJ+IPt4mwmubAIvQfIX6AkAF69VRnssn9VoHK0veXSraBrwI3AlLAVG1dQ==","shasum":"d0768ce7f001d262240082526254944b344779f4","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.1.0.tgz","fileCount":102,"unpackedSize":1402288,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIFKsxC7moLer/NPh40y1JUS7V/EeyzEAempHMijN0t4UAiEAujJlAFp4PxXCATCPVvSu0e9rU1laqWf5xk5Bm88PzR8="}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.1.0_1705997894973_0.2878663748674146"},"_hasShrinkwrap":false},"1.1.1":{"name":"l-fen","version":"1.1.1","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"postinstall":"npx simple-git-hooks","test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.9.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","uuid":"^9.0.1","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_id":"l-fen@1.1.1","gitHead":"3b9efe89faffc143e6ba9f2980c86a7ea0f04cb8","types":"./lib/index.d.ts","_nodeVersion":"18.19.0","_npmVersion":"9.9.2","dist":{"integrity":"sha512-70xqSzexYOxnljKMF04JzPIDRvklL70jnTC+dMt2Xah8nQkOWF0kKvg23fUZVWbahN25nTA/pQAz8cNpe+i7Dg==","shasum":"b1b3e12ae9606be9896f0bffdcc312952d383300","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.1.1.tgz","fileCount":102,"unpackedSize":1448234,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIAO85XxMb8b93wkJPHppzjaxIQMilSLcKORb430WCmlaAiEA9FkqXb6ZYWsF5R/eiRCjrcE3NsvMtEC+uQByT4pvUt0="}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.1.1_1705999808611_0.2807260723189664"},"_hasShrinkwrap":false},"1.1.2":{"name":"l-fen","version":"1.1.2","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"postinstall":"npx simple-git-hooks","test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.9.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","uuid":"^9.0.1","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_id":"l-fen@1.1.2","gitHead":"b48356f9e45eeda3469a72735ef1f3364d344ca7","types":"./lib/index.d.ts","_nodeVersion":"18.19.0","_npmVersion":"9.9.2","dist":{"integrity":"sha512-gQUFLoq+BLX0S/A0VPjE7rR3/nxqCNJOdhk4C90jEu91tK0coMMYn7MZXsJrc9I0ctvIF2Euv8SlliGsicfxKQ==","shasum":"db064dfdde0945f542b17ea14c6e57299108431d","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.1.2.tgz","fileCount":102,"unpackedSize":1449283,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIDoHWGK4BQLvy2o7WyvdmPGnKmnrv5otHGM4XPraXQszAiEA0Emi/jXyZSK/bcIB6v9r8f81Nm7E1WIT3rHAxui9raI="}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.1.2_1706003328012_0.6653876671349497"},"_hasShrinkwrap":false},"1.1.3":{"name":"l-fen","version":"1.1.3","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"postinstall":"npx simple-git-hooks","test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.9.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","uuid":"^9.0.1","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_id":"l-fen@1.1.3","gitHead":"5a6d55a9a1a312d13f2d14341e45a3b0f1b2d778","types":"./lib/index.d.ts","_nodeVersion":"18.19.0","_npmVersion":"9.9.2","dist":{"integrity":"sha512-fpFD9Jj0uF9kPDJjxpHCrmGknQlDiQfKdBIaoaFundlOA6Iwh0CAeOrJvDJ+fj7Zk1kCGd2uRQ1n1Qac9+EzEQ==","shasum":"bbf8e0a596700e26342b9972b21c84a01ad245d9","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.1.3.tgz","fileCount":102,"unpackedSize":1449823,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQC9O6O8AvL46UdzMYwWjJclB8o32Y8wfDzFg7e1sudCQAIgHtjBdz7/tHnDLhE925jvegGqNiLK/ESumMBBlQla7Hc="}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.1.3_1707071955539_0.20481230675481443"},"_hasShrinkwrap":false},"1.1.4":{"name":"l-fen","version":"1.1.4","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.9.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","uuid":"^9.0.1","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_id":"l-fen@1.1.4","gitHead":"29beee97a62a90cd95fa8bef77acb0e9b396614c","types":"./lib/index.d.ts","_nodeVersion":"18.19.1","_npmVersion":"9.9.2","dist":{"integrity":"sha512-9oPr31t9yVEALqllH7Yykj41I8GGpcFkf1k6LyI1Cx3+BJTstHrECsQkmiLbPH2ox9ZE/S9jJna1y2lIk9NPug==","shasum":"dbdcb16c1fe0e52d0c6c1e888b68f90b444ee340","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.1.4.tgz","fileCount":102,"unpackedSize":1450001,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEYCIQDUnowt0g/E7nLfB+5HmFNdXGD7zkdPj/8uW3Erk/s4YQIhALaI9yjN4i3ONgF0hMnm+N1grGs8gQRXeQBdHLz6Zl/3"}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.1.4_1710840429384_0.793793545859147"},"_hasShrinkwrap":false},"1.2.0-beta.1":{"name":"l-fen","version":"1.2.0-beta.1","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.9.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","fast-crc32c":"^2.0.0","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","uuid":"^9.0.1","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_id":"l-fen@1.2.0-beta.1","readme":"## Description\n\n**l-fen** is a small runtime to execute a bundle of Serverless Functions like they were running in the cloud.\n\n:warning: only [GCP](https://cloud.google.com/) supported right now\n\n## Motivation\n\nWhen working on backend services and applications built by a multitude of individual Serverless Functions with various triggers, like HTTP, Pub/Sub or Storage/Bucket-Events, I always struggled with testing the whole setup locally.  \nEven if there's a 100% test coverage for each function itself, one function of the whole application might expect a different event than another function publishes.\n\nSo my approach was always to test the code itself with unit and e2e tests, deploy to a test or dev environment and test the whole process of the application again. If something fails, I had to look into the logs, hoping that I logged enough to narrow down the problem and ideally get the line where my code broke or wasn't working as expected.\n\nAfter a while, I decided to write something like an emulator for the various triggers and events.  \nThe goal was to locally run my backend service, which had several HTTP functions writing files to buckets, triggering other functions, publishing events via Pub/Sub with other functions subscribing to these.\n\nIn the end, I came up with **l-fen**, the _local function environment_.\n\n## Usage\n\nFirst, you have to build your Functions, since **l-fen** `requires()` the JS file.\n\nWhen that's done, you can start to create your `.fenrc.json` file.  \nThis file contains the whole configuration of your setup and the individual Functions.\nIt's required to to tell **l-fen** which Functions to load and when to call these.\n\nWhen everything is set up, just call `npx l-fen` to do it's magic.\n\n### Configuration\n\n```jsonc\n{\n  // the local folder containg all buckets and their files\n  \"storageDir\": \"./local-test-storage\",\n  // the port of the proxy, handling all HTTP requests and forwarding to the related HTTP Functions\n  \"httpProxyPort\": \"8080\",\n  // the port of the gRPC server handling stuff like Pub/Sub\n  \"grpcProxyPort\": \"50051\",\n  // the log level for l-fen. To print logs, created by Functions, set it to \"verbose\" or lower\n  \"loglevel\": \"verbose\",\n  // configuration for the Cloud Provider\n  \"provider\": {\n    // the Cloud Provider itself, right now, only GCP is supported\n    \"name\": \"GCP\",\n    // name of the project, used for some internal authentication processes\n    \"project\": \"my-test-project\"\n  },\n  // list of all Functions to be managed by l-fen\n  \"services\": [\n    {\n      // name of the Function, in case of HTTP Functions, it serves as the base route for the proxy\n      \"name\": \"http-handler\",\n      // path to the built main JS file\n      \"path\": \"./http-handler/index.js\",\n      // the exported entry point of the function\n      \"entryPoint\": \"handler\",\n      // the type of the function, \"HTTP\", \"PUBSUB\" or \"BUCKET\"\n      \"triggerType\": \"HTTP\",\n      // environment variables to be used by the Function\n      \"envVars\": {\n        \"PUBSUB_API_ENDPOINT\": \"localhost:50051\",\n        \"PROJECT_ID\": \"my-test-project\",\n        \"PUBSUB_TOPIC\": \"generic-topic\"\n      }\n    },\n    {\n      \"name\": \"file-writer\",\n      \"path\": \"./file-writer/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"PUBSUB\",\n      // if triggerType is either \"PUBSUB\" or \"BUCKET\", additional configuration has to be done here.\n      \"triggerConfig\": {\n        // the topic, the Pub/Sub function should subscribe\n        \"topic\": \"generic-topic\"\n      },\n      \"envVars\": {\n        \"MY_BUCKET\": \"my-bucket\"\n      }\n    },\n    {\n      \"name\": \"file-reader\",\n      \"path\": \"./file-reader/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"BUCKET\",\n      \"triggerConfig\": {\n        // file events the Function should listen on. Right now, only \"WRITE\" is supported\n        \"events\": [\"WRITE\"],\n        // the bucket the Function should listen on.\n        \"targetBucket\": \"my-bucket\"\n      },\n      \"envVars\": {}\n    }\n  ],\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n\n## Restrictions\n\n### Cloud Providers\n\nRight now, **l-fen** is only capable of handling Google Cloud Functions.  \nOther Cloud Providers are not yet supported.\n\n### Pub/Sub\n\nThe original idea was, to use **l-fen** out of the box, without the need of changing the Functions code to work with **l-fen**.\nUnfortunately, I didn't achieve that completely for Pub/Sub.\n\nTo allow **l-fen** to handle published event, the publisher has to use the gRPC server provided by **l-fen**.  \nWhen using the [Google Cloud Pub/Sub:Node.js Client](https://www.npmjs.com/package/@google-cloud/pubsub), the endpoint can be changed by instantiating the client with `new PubSub({apiEndpoint: 'localhost:50051'})`.\n\nI suggest to introduce an environment variable to the Function publishing events, to dynamically override the default endpoint and set that environment variable within the functions configuration in the `.fenrc.json`.\n\n## Under the hood\n\n**l-fen** consists of two main parts, the **orchestrator** and the **bootloader**, the brain and the heart of **l-fen**.\n\nThe orchestrator, the brain, plans the whole environment and knows what to do in certain cases and events.\nThe bootloader, the heart, is executing the Functions and providing all necessary data.\n\nThe orchestrator spawns new bootloader processes by executing the bootloader with required arguments.\n\n### The orchestrator\n\nThe orchestrator is responsible for loading the `.fenrc.json`, managing the Functions and setting up the local environment itself.\n\nDepending on the type of Functions, it adds a file watcher to the `storageDir` and calls the bootloader of the Functions that might be triggered by certain events in buckets.  \nThe same applies to HTTP and Pub/Sub Functions. The orchestrator provides a HTTP proxy and forwards any requests to the responsible Functions. It also starts the gRPC server that can be used by Pub/Sub to handle events and topics and call the according Functions.\n\n### The bootloader\n\nThe bootloader is responsible for starting a Function and providing all the required stuff the Function needs.\nIt's really nothing else like a small CLI tool, that expects certain arguments, like the payload it should forward to the Function or the environment variables the Function should be provided with.\n\nFor bucket and Pub/Sub Functions, it's quite straight forward. It `requires()` the Functions JS file, mapping the payload to the corresponding object and calling the `entryPoint` method with that object.\n\nIn case of an HTTP Function, the bootloader makes use of [express](https://www.npmjs.com/package/express) and [SuperTest](https://www.npmjs.com/package/supertest) to create a proper request and handle stuff like different HTTP methods and headers.\n\nThe orchestrator is loading the `.fenrc.json`, managing all Functions, providing some basic setup like configuring a fake service account and file watcher on the specified `storageDir` and start the HTTP proxy.\n\nIt's also possible to start the bootloader without the orchestrator by running something like\n`npx l-fen-boot --event='{\\\"bucket\\\": \\\"user\\\", \\\"name\\\": \\\"111/b.json\\\"}' --path=\"../../../path/to/function/index.js\" --entryPoint=\"handleEvent\" --name=\"name-of-the-function\"`\n\n### Custom extension\n\nl-fen allows adding custom extensions that are executed for each loaded Function right before starting it.\n\nCreate your extension by exporting a class implementing the `LFenExtension` interface from `l-fen/types/extension.interface` and adding the absolute path to the config:\n\n```jsonc\n{\n  // all the other stuff, see above\n  ...\n  // here we add out extension\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n","readmeFilename":"README.md","gitHead":"c971bf676fa5043836172a6ffb187a31e9f0dda8","types":"./lib/index.d.ts","_nodeVersion":"18.19.1","_npmVersion":"9.9.2","dist":{"integrity":"sha512-sCYOLFbsbDm+TKqOM5jSOKJ4W7aMx+zrvaXMC7HTuRg+jmWHQ/hrmll/TTzKDh9xg+ibAgW0JPLy79ezEG3awA==","shasum":"52fbf9e79a344723ee0d298933cf8a5659fedb8d","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.2.0-beta.1.tgz","fileCount":102,"unpackedSize":1451977,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIHEllYp6+gGixjFvaCrj1dMf+mX/xBmo6gWlb+x7jaHaAiEAwuA8F+Gp4iQq5Vxtp447gidpCoWfn+89RiG2Ofc8jT0="}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.2.0-beta.1_1710849834858_0.38465000596423193"},"_hasShrinkwrap":false},"1.2.0-beta.2":{"name":"l-fen","version":"1.2.0-beta.2","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.9.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@google-cloud/storage":"^7.9.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","uuid":"^9.0.1","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_id":"l-fen@1.2.0-beta.2","readme":"## Description\n\n**l-fen** is a small runtime to execute a bundle of Serverless Functions like they were running in the cloud.\n\n:warning: only [GCP](https://cloud.google.com/) supported right now\n\n## Motivation\n\nWhen working on backend services and applications built by a multitude of individual Serverless Functions with various triggers, like HTTP, Pub/Sub or Storage/Bucket-Events, I always struggled with testing the whole setup locally.  \nEven if there's a 100% test coverage for each function itself, one function of the whole application might expect a different event than another function publishes.\n\nSo my approach was always to test the code itself with unit and e2e tests, deploy to a test or dev environment and test the whole process of the application again. If something fails, I had to look into the logs, hoping that I logged enough to narrow down the problem and ideally get the line where my code broke or wasn't working as expected.\n\nAfter a while, I decided to write something like an emulator for the various triggers and events.  \nThe goal was to locally run my backend service, which had several HTTP functions writing files to buckets, triggering other functions, publishing events via Pub/Sub with other functions subscribing to these.\n\nIn the end, I came up with **l-fen**, the _local function environment_.\n\n## Usage\n\nFirst, you have to build your Functions, since **l-fen** `requires()` the JS file.\n\nWhen that's done, you can start to create your `.fenrc.json` file.  \nThis file contains the whole configuration of your setup and the individual Functions.\nIt's required to to tell **l-fen** which Functions to load and when to call these.\n\nWhen everything is set up, just call `npx l-fen` to do it's magic.\n\n### Configuration\n\n```jsonc\n{\n  // the local folder containg all buckets and their files\n  \"storageDir\": \"./local-test-storage\",\n  // the port of the proxy, handling all HTTP requests and forwarding to the related HTTP Functions\n  \"httpProxyPort\": \"8080\",\n  // the port of the gRPC server handling stuff like Pub/Sub\n  \"grpcProxyPort\": \"50051\",\n  // the log level for l-fen. To print logs, created by Functions, set it to \"verbose\" or lower\n  \"loglevel\": \"verbose\",\n  // configuration for the Cloud Provider\n  \"provider\": {\n    // the Cloud Provider itself, right now, only GCP is supported\n    \"name\": \"GCP\",\n    // name of the project, used for some internal authentication processes\n    \"project\": \"my-test-project\"\n  },\n  // list of all Functions to be managed by l-fen\n  \"services\": [\n    {\n      // name of the Function, in case of HTTP Functions, it serves as the base route for the proxy\n      \"name\": \"http-handler\",\n      // path to the built main JS file\n      \"path\": \"./http-handler/index.js\",\n      // the exported entry point of the function\n      \"entryPoint\": \"handler\",\n      // the type of the function, \"HTTP\", \"PUBSUB\" or \"BUCKET\"\n      \"triggerType\": \"HTTP\",\n      // environment variables to be used by the Function\n      \"envVars\": {\n        \"PUBSUB_API_ENDPOINT\": \"localhost:50051\",\n        \"PROJECT_ID\": \"my-test-project\",\n        \"PUBSUB_TOPIC\": \"generic-topic\"\n      }\n    },\n    {\n      \"name\": \"file-writer\",\n      \"path\": \"./file-writer/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"PUBSUB\",\n      // if triggerType is either \"PUBSUB\" or \"BUCKET\", additional configuration has to be done here.\n      \"triggerConfig\": {\n        // the topic, the Pub/Sub function should subscribe\n        \"topic\": \"generic-topic\"\n      },\n      \"envVars\": {\n        \"MY_BUCKET\": \"my-bucket\"\n      }\n    },\n    {\n      \"name\": \"file-reader\",\n      \"path\": \"./file-reader/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"BUCKET\",\n      \"triggerConfig\": {\n        // file events the Function should listen on. Right now, only \"WRITE\" is supported\n        \"events\": [\"WRITE\"],\n        // the bucket the Function should listen on.\n        \"targetBucket\": \"my-bucket\"\n      },\n      \"envVars\": {}\n    }\n  ],\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n\n## Restrictions\n\n### Cloud Providers\n\nRight now, **l-fen** is only capable of handling Google Cloud Functions.  \nOther Cloud Providers are not yet supported.\n\n### Pub/Sub\n\nThe original idea was, to use **l-fen** out of the box, without the need of changing the Functions code to work with **l-fen**.\nUnfortunately, I didn't achieve that completely for Pub/Sub.\n\nTo allow **l-fen** to handle published event, the publisher has to use the gRPC server provided by **l-fen**.  \nWhen using the [Google Cloud Pub/Sub:Node.js Client](https://www.npmjs.com/package/@google-cloud/pubsub), the endpoint can be changed by instantiating the client with `new PubSub({apiEndpoint: 'localhost:50051'})`.\n\nI suggest to introduce an environment variable to the Function publishing events, to dynamically override the default endpoint and set that environment variable within the functions configuration in the `.fenrc.json`.\n\n## Under the hood\n\n**l-fen** consists of two main parts, the **orchestrator** and the **bootloader**, the brain and the heart of **l-fen**.\n\nThe orchestrator, the brain, plans the whole environment and knows what to do in certain cases and events.\nThe bootloader, the heart, is executing the Functions and providing all necessary data.\n\nThe orchestrator spawns new bootloader processes by executing the bootloader with required arguments.\n\n### The orchestrator\n\nThe orchestrator is responsible for loading the `.fenrc.json`, managing the Functions and setting up the local environment itself.\n\nDepending on the type of Functions, it adds a file watcher to the `storageDir` and calls the bootloader of the Functions that might be triggered by certain events in buckets.  \nThe same applies to HTTP and Pub/Sub Functions. The orchestrator provides a HTTP proxy and forwards any requests to the responsible Functions. It also starts the gRPC server that can be used by Pub/Sub to handle events and topics and call the according Functions.\n\n### The bootloader\n\nThe bootloader is responsible for starting a Function and providing all the required stuff the Function needs.\nIt's really nothing else like a small CLI tool, that expects certain arguments, like the payload it should forward to the Function or the environment variables the Function should be provided with.\n\nFor bucket and Pub/Sub Functions, it's quite straight forward. It `requires()` the Functions JS file, mapping the payload to the corresponding object and calling the `entryPoint` method with that object.\n\nIn case of an HTTP Function, the bootloader makes use of [express](https://www.npmjs.com/package/express) and [SuperTest](https://www.npmjs.com/package/supertest) to create a proper request and handle stuff like different HTTP methods and headers.\n\nThe orchestrator is loading the `.fenrc.json`, managing all Functions, providing some basic setup like configuring a fake service account and file watcher on the specified `storageDir` and start the HTTP proxy.\n\nIt's also possible to start the bootloader without the orchestrator by running something like\n`npx l-fen-boot --event='{\\\"bucket\\\": \\\"user\\\", \\\"name\\\": \\\"111/b.json\\\"}' --path=\"../../../path/to/function/index.js\" --entryPoint=\"handleEvent\" --name=\"name-of-the-function\"`\n\n### Custom extension\n\nl-fen allows adding custom extensions that are executed for each loaded Function right before starting it.\n\nCreate your extension by exporting a class implementing the `LFenExtension` interface from `l-fen/types/extension.interface` and adding the absolute path to the config:\n\n```jsonc\n{\n  // all the other stuff, see above\n  ...\n  // here we add out extension\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n","readmeFilename":"README.md","gitHead":"a8c77874e68a613158d80346c6d6896f2fe71617","types":"./lib/index.d.ts","_nodeVersion":"18.19.1","_npmVersion":"9.9.2","dist":{"integrity":"sha512-twkAbagR9mA5QoAx+uyrGKvCG7Fh24NfLSDIMMgd4p5agW8Y2u/aPToSxiyLfe5qJ/ipJFj7qAoleEvEVueqnA==","shasum":"33b8a1cc42b116a7add0a188276848d54102b9ae","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.2.0-beta.2.tgz","fileCount":102,"unpackedSize":1450170,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDyA9kS1fYviINmUiGpD7Ny/kiBc4EqCY7BkpNZczU1ygIgdgVjaew0zCmUTA9gJq3hjBdjq6XMgmLNQhQH/1PGHsM="}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.2.0-beta.2_1710855533710_0.20203698852625784"},"_hasShrinkwrap":false},"1.2.0-beta.3":{"name":"l-fen","version":"1.2.0-beta.3","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.9.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@google-cloud/storage":"^7.9.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","uuid":"^9.0.1","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_id":"l-fen@1.2.0-beta.3","readme":"## Description\n\n**l-fen** is a small runtime to execute a bundle of Serverless Functions like they were running in the cloud.\n\n:warning: only [GCP](https://cloud.google.com/) supported right now\n\n## Motivation\n\nWhen working on backend services and applications built by a multitude of individual Serverless Functions with various triggers, like HTTP, Pub/Sub or Storage/Bucket-Events, I always struggled with testing the whole setup locally.  \nEven if there's a 100% test coverage for each function itself, one function of the whole application might expect a different event than another function publishes.\n\nSo my approach was always to test the code itself with unit and e2e tests, deploy to a test or dev environment and test the whole process of the application again. If something fails, I had to look into the logs, hoping that I logged enough to narrow down the problem and ideally get the line where my code broke or wasn't working as expected.\n\nAfter a while, I decided to write something like an emulator for the various triggers and events.  \nThe goal was to locally run my backend service, which had several HTTP functions writing files to buckets, triggering other functions, publishing events via Pub/Sub with other functions subscribing to these.\n\nIn the end, I came up with **l-fen**, the _local function environment_.\n\n## Usage\n\nFirst, you have to build your Functions, since **l-fen** `requires()` the JS file.\n\nWhen that's done, you can start to create your `.fenrc.json` file.  \nThis file contains the whole configuration of your setup and the individual Functions.\nIt's required to to tell **l-fen** which Functions to load and when to call these.\n\nWhen everything is set up, just call `npx l-fen` to do it's magic.\n\n### Configuration\n\n```jsonc\n{\n  // the local folder containg all buckets and their files\n  \"storageDir\": \"./local-test-storage\",\n  // the port of the proxy, handling all HTTP requests and forwarding to the related HTTP Functions\n  \"httpProxyPort\": \"8080\",\n  // the port of the gRPC server handling stuff like Pub/Sub\n  \"grpcProxyPort\": \"50051\",\n  // the log level for l-fen. To print logs, created by Functions, set it to \"verbose\" or lower\n  \"loglevel\": \"verbose\",\n  // configuration for the Cloud Provider\n  \"provider\": {\n    // the Cloud Provider itself, right now, only GCP is supported\n    \"name\": \"GCP\",\n    // name of the project, used for some internal authentication processes\n    \"project\": \"my-test-project\"\n  },\n  // list of all Functions to be managed by l-fen\n  \"services\": [\n    {\n      // name of the Function, in case of HTTP Functions, it serves as the base route for the proxy\n      \"name\": \"http-handler\",\n      // path to the built main JS file\n      \"path\": \"./http-handler/index.js\",\n      // the exported entry point of the function\n      \"entryPoint\": \"handler\",\n      // the type of the function, \"HTTP\", \"PUBSUB\" or \"BUCKET\"\n      \"triggerType\": \"HTTP\",\n      // environment variables to be used by the Function\n      \"envVars\": {\n        \"PUBSUB_API_ENDPOINT\": \"localhost:50051\",\n        \"PROJECT_ID\": \"my-test-project\",\n        \"PUBSUB_TOPIC\": \"generic-topic\"\n      }\n    },\n    {\n      \"name\": \"file-writer\",\n      \"path\": \"./file-writer/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"PUBSUB\",\n      // if triggerType is either \"PUBSUB\" or \"BUCKET\", additional configuration has to be done here.\n      \"triggerConfig\": {\n        // the topic, the Pub/Sub function should subscribe\n        \"topic\": \"generic-topic\"\n      },\n      \"envVars\": {\n        \"MY_BUCKET\": \"my-bucket\"\n      }\n    },\n    {\n      \"name\": \"file-reader\",\n      \"path\": \"./file-reader/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"BUCKET\",\n      \"triggerConfig\": {\n        // file events the Function should listen on. Right now, only \"WRITE\" is supported\n        \"events\": [\"WRITE\"],\n        // the bucket the Function should listen on.\n        \"targetBucket\": \"my-bucket\"\n      },\n      \"envVars\": {}\n    }\n  ],\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n\n## Restrictions\n\n### Cloud Providers\n\nRight now, **l-fen** is only capable of handling Google Cloud Functions.  \nOther Cloud Providers are not yet supported.\n\n### Pub/Sub\n\nThe original idea was, to use **l-fen** out of the box, without the need of changing the Functions code to work with **l-fen**.\nUnfortunately, I didn't achieve that completely for Pub/Sub.\n\nTo allow **l-fen** to handle published event, the publisher has to use the gRPC server provided by **l-fen**.  \nWhen using the [Google Cloud Pub/Sub:Node.js Client](https://www.npmjs.com/package/@google-cloud/pubsub), the endpoint can be changed by instantiating the client with `new PubSub({apiEndpoint: 'localhost:50051'})`.\n\nI suggest to introduce an environment variable to the Function publishing events, to dynamically override the default endpoint and set that environment variable within the functions configuration in the `.fenrc.json`.\n\n## Under the hood\n\n**l-fen** consists of two main parts, the **orchestrator** and the **bootloader**, the brain and the heart of **l-fen**.\n\nThe orchestrator, the brain, plans the whole environment and knows what to do in certain cases and events.\nThe bootloader, the heart, is executing the Functions and providing all necessary data.\n\nThe orchestrator spawns new bootloader processes by executing the bootloader with required arguments.\n\n### The orchestrator\n\nThe orchestrator is responsible for loading the `.fenrc.json`, managing the Functions and setting up the local environment itself.\n\nDepending on the type of Functions, it adds a file watcher to the `storageDir` and calls the bootloader of the Functions that might be triggered by certain events in buckets.  \nThe same applies to HTTP and Pub/Sub Functions. The orchestrator provides a HTTP proxy and forwards any requests to the responsible Functions. It also starts the gRPC server that can be used by Pub/Sub to handle events and topics and call the according Functions.\n\n### The bootloader\n\nThe bootloader is responsible for starting a Function and providing all the required stuff the Function needs.\nIt's really nothing else like a small CLI tool, that expects certain arguments, like the payload it should forward to the Function or the environment variables the Function should be provided with.\n\nFor bucket and Pub/Sub Functions, it's quite straight forward. It `requires()` the Functions JS file, mapping the payload to the corresponding object and calling the `entryPoint` method with that object.\n\nIn case of an HTTP Function, the bootloader makes use of [express](https://www.npmjs.com/package/express) and [SuperTest](https://www.npmjs.com/package/supertest) to create a proper request and handle stuff like different HTTP methods and headers.\n\nThe orchestrator is loading the `.fenrc.json`, managing all Functions, providing some basic setup like configuring a fake service account and file watcher on the specified `storageDir` and start the HTTP proxy.\n\nIt's also possible to start the bootloader without the orchestrator by running something like\n`npx l-fen-boot --event='{\\\"bucket\\\": \\\"user\\\", \\\"name\\\": \\\"111/b.json\\\"}' --path=\"../../../path/to/function/index.js\" --entryPoint=\"handleEvent\" --name=\"name-of-the-function\"`\n\n### Custom extension\n\nl-fen allows adding custom extensions that are executed for each loaded Function right before starting it.\n\nCreate your extension by exporting a class implementing the `LFenExtension` interface from `l-fen/types/extension.interface` and adding the absolute path to the config:\n\n```jsonc\n{\n  // all the other stuff, see above\n  ...\n  // here we add out extension\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n","readmeFilename":"README.md","gitHead":"eef2bf7a846c9db69fac0c6c37d4d9144166e85c","types":"./lib/index.d.ts","_nodeVersion":"18.19.1","_npmVersion":"9.9.2","dist":{"integrity":"sha512-cCM8tzVG9z1WdS2MppWeFlakAYuqUGBKwSjjQH7w4DxwgWUwFrWmG3fPAoaMDPBz8Ejs6se8FEXH/fr5yFBt0A==","shasum":"7019d7f00be347cca69160dbb832ddf0c608b442","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.2.0-beta.3.tgz","fileCount":102,"unpackedSize":1450375,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIGfaE6jIWAK/P1YnULqDKAcpAYGRI7de0GMmLIS5gYy1AiEAmF7u1M/w/OXk0v5SzG/AofeNkwS/tK5lDczDDgSO8uc="}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.2.0-beta.3_1710856173601_0.15545689723698386"},"_hasShrinkwrap":false},"1.2.0-beta.4":{"name":"l-fen","version":"1.2.0-beta.4","description":"sandbox to simulate full serverless function environment","main":"lib/index.js","scripts":{"test":"echo \"Error: no test specified\" && exit 1","build:proto":". ./scripts/proto-build.sh","build":"npm run build:proto && npx tsc","postbuild":". ./scripts/proto-copy.sh","start":"node lib/index.js","semantic-release":"semantic-release"},"bin":{"l-fen-boot":"lib/service/bootloader.js","l-fen":"lib/index.js"},"repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"license":"MIT","bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"homepage":"https://github.com/Achneoder/l-fen#readme","devDependencies":{"@semantic-release/changelog":"^6.0.1","@semantic-release/git":"^10.0.1","@types/express":"^4.17.11","@types/node":"^18.0.0","@types/supertest":"^2.0.12","@types/yargs":"^17.0.0","@typescript-eslint/eslint-plugin":"^5.0.0","@typescript-eslint/parser":"^5.0.0","eslint":"^8.0.0","eslint-config-prettier":"^8.1.0","grpc_tools_node_protoc_ts":"^5.3.2","grpc-tools":"^1.11.3","lint-staged":"^13.0.0","prettier":"^2.2.1","semantic-release":"^21.0.0","simple-git-hooks":"^2.9.0","typescript":"^5.0.0"},"dependencies":{"@google-cloud/pubsub":"^3.2.0","@google-cloud/storage":"^7.9.0","@grpc/grpc-js":"^1.7.2","@grpc/proto-loader":"^0.7.3","body-parser":"^1.20.1","chokidar":"^3.5.1","express":"^4.17.1","glob":"^9.0.0","google-gax":"^3.5.2","google-protobuf":"^3.21.2","md5-file":"^5.0.0","nock":"^13.0.11","supertest":"^6.3.0","uuid":"^9.0.1","winston":"^3.8.2","yargs":"^17.0.0"},"lint-staged":{"*.ts":"eslint --cache --fix","*.{js,ts,css,md}":"prettier --write"},"simple-git-hooks":{"pre-commit":"npx lint-staged"},"_id":"l-fen@1.2.0-beta.4","readme":"## Description\n\n**l-fen** is a small runtime to execute a bundle of Serverless Functions like they were running in the cloud.\n\n:warning: only [GCP](https://cloud.google.com/) supported right now\n\n## Motivation\n\nWhen working on backend services and applications built by a multitude of individual Serverless Functions with various triggers, like HTTP, Pub/Sub or Storage/Bucket-Events, I always struggled with testing the whole setup locally.  \nEven if there's a 100% test coverage for each function itself, one function of the whole application might expect a different event than another function publishes.\n\nSo my approach was always to test the code itself with unit and e2e tests, deploy to a test or dev environment and test the whole process of the application again. If something fails, I had to look into the logs, hoping that I logged enough to narrow down the problem and ideally get the line where my code broke or wasn't working as expected.\n\nAfter a while, I decided to write something like an emulator for the various triggers and events.  \nThe goal was to locally run my backend service, which had several HTTP functions writing files to buckets, triggering other functions, publishing events via Pub/Sub with other functions subscribing to these.\n\nIn the end, I came up with **l-fen**, the _local function environment_.\n\n## Usage\n\nFirst, you have to build your Functions, since **l-fen** `requires()` the JS file.\n\nWhen that's done, you can start to create your `.fenrc.json` file.  \nThis file contains the whole configuration of your setup and the individual Functions.\nIt's required to to tell **l-fen** which Functions to load and when to call these.\n\nWhen everything is set up, just call `npx l-fen` to do it's magic.\n\n### Configuration\n\n```jsonc\n{\n  // the local folder containg all buckets and their files\n  \"storageDir\": \"./local-test-storage\",\n  // the port of the proxy, handling all HTTP requests and forwarding to the related HTTP Functions\n  \"httpProxyPort\": \"8080\",\n  // the port of the gRPC server handling stuff like Pub/Sub\n  \"grpcProxyPort\": \"50051\",\n  // the log level for l-fen. To print logs, created by Functions, set it to \"verbose\" or lower\n  \"loglevel\": \"verbose\",\n  // configuration for the Cloud Provider\n  \"provider\": {\n    // the Cloud Provider itself, right now, only GCP is supported\n    \"name\": \"GCP\",\n    // name of the project, used for some internal authentication processes\n    \"project\": \"my-test-project\"\n  },\n  // list of all Functions to be managed by l-fen\n  \"services\": [\n    {\n      // name of the Function, in case of HTTP Functions, it serves as the base route for the proxy\n      \"name\": \"http-handler\",\n      // path to the built main JS file\n      \"path\": \"./http-handler/index.js\",\n      // the exported entry point of the function\n      \"entryPoint\": \"handler\",\n      // the type of the function, \"HTTP\", \"PUBSUB\" or \"BUCKET\"\n      \"triggerType\": \"HTTP\",\n      // environment variables to be used by the Function\n      \"envVars\": {\n        \"PUBSUB_API_ENDPOINT\": \"localhost:50051\",\n        \"PROJECT_ID\": \"my-test-project\",\n        \"PUBSUB_TOPIC\": \"generic-topic\"\n      }\n    },\n    {\n      \"name\": \"file-writer\",\n      \"path\": \"./file-writer/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"PUBSUB\",\n      // if triggerType is either \"PUBSUB\" or \"BUCKET\", additional configuration has to be done here.\n      \"triggerConfig\": {\n        // the topic, the Pub/Sub function should subscribe\n        \"topic\": \"generic-topic\"\n      },\n      \"envVars\": {\n        \"MY_BUCKET\": \"my-bucket\"\n      }\n    },\n    {\n      \"name\": \"file-reader\",\n      \"path\": \"./file-reader/index.js\",\n      \"entryPoint\": \"handler\",\n      \"triggerType\": \"BUCKET\",\n      \"triggerConfig\": {\n        // file events the Function should listen on. Right now, only \"WRITE\" is supported\n        \"events\": [\"WRITE\"],\n        // the bucket the Function should listen on.\n        \"targetBucket\": \"my-bucket\"\n      },\n      \"envVars\": {}\n    }\n  ],\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n\n## Restrictions\n\n### Cloud Providers\n\nRight now, **l-fen** is only capable of handling Google Cloud Functions.  \nOther Cloud Providers are not yet supported.\n\n### Pub/Sub\n\nThe original idea was, to use **l-fen** out of the box, without the need of changing the Functions code to work with **l-fen**.\nUnfortunately, I didn't achieve that completely for Pub/Sub.\n\nTo allow **l-fen** to handle published event, the publisher has to use the gRPC server provided by **l-fen**.  \nWhen using the [Google Cloud Pub/Sub:Node.js Client](https://www.npmjs.com/package/@google-cloud/pubsub), the endpoint can be changed by instantiating the client with `new PubSub({apiEndpoint: 'localhost:50051'})`.\n\nI suggest to introduce an environment variable to the Function publishing events, to dynamically override the default endpoint and set that environment variable within the functions configuration in the `.fenrc.json`.\n\n## Under the hood\n\n**l-fen** consists of two main parts, the **orchestrator** and the **bootloader**, the brain and the heart of **l-fen**.\n\nThe orchestrator, the brain, plans the whole environment and knows what to do in certain cases and events.\nThe bootloader, the heart, is executing the Functions and providing all necessary data.\n\nThe orchestrator spawns new bootloader processes by executing the bootloader with required arguments.\n\n### The orchestrator\n\nThe orchestrator is responsible for loading the `.fenrc.json`, managing the Functions and setting up the local environment itself.\n\nDepending on the type of Functions, it adds a file watcher to the `storageDir` and calls the bootloader of the Functions that might be triggered by certain events in buckets.  \nThe same applies to HTTP and Pub/Sub Functions. The orchestrator provides a HTTP proxy and forwards any requests to the responsible Functions. It also starts the gRPC server that can be used by Pub/Sub to handle events and topics and call the according Functions.\n\n### The bootloader\n\nThe bootloader is responsible for starting a Function and providing all the required stuff the Function needs.\nIt's really nothing else like a small CLI tool, that expects certain arguments, like the payload it should forward to the Function or the environment variables the Function should be provided with.\n\nFor bucket and Pub/Sub Functions, it's quite straight forward. It `requires()` the Functions JS file, mapping the payload to the corresponding object and calling the `entryPoint` method with that object.\n\nIn case of an HTTP Function, the bootloader makes use of [express](https://www.npmjs.com/package/express) and [SuperTest](https://www.npmjs.com/package/supertest) to create a proper request and handle stuff like different HTTP methods and headers.\n\nThe orchestrator is loading the `.fenrc.json`, managing all Functions, providing some basic setup like configuring a fake service account and file watcher on the specified `storageDir` and start the HTTP proxy.\n\nIt's also possible to start the bootloader without the orchestrator by running something like\n`npx l-fen-boot --event='{\\\"bucket\\\": \\\"user\\\", \\\"name\\\": \\\"111/b.json\\\"}' --path=\"../../../path/to/function/index.js\" --entryPoint=\"handleEvent\" --name=\"name-of-the-function\"`\n\n### Custom extension\n\nl-fen allows adding custom extensions that are executed for each loaded Function right before starting it.\n\nCreate your extension by exporting a class implementing the `LFenExtension` interface from `l-fen/types/extension.interface` and adding the absolute path to the config:\n\n```jsonc\n{\n  // all the other stuff, see above\n  ...\n  // here we add out extension\n  \"extensions\": [\"/path/to/my/extension.js\"]\n}\n```\n","readmeFilename":"README.md","gitHead":"a385365f664332317fbeba0259f6f5bc1c44ec07","types":"./lib/index.d.ts","_nodeVersion":"18.19.1","_npmVersion":"9.9.2","dist":{"integrity":"sha512-CnOoQIDiCFdoesCHrNURCBHfyufcCAZMW9f5e3w83G+PzhdFxjBVobCm68DyIiL5GY0TLkDreFLlun8lFpfLJQ==","shasum":"b77088e88ad489cd988881b04ef60eda635c0041","tarball":"https://registry.npmjs.org/l-fen/-/l-fen-1.2.0-beta.4.tgz","fileCount":102,"unpackedSize":1454220,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCIDHH5Rm0o7Y5ZB0R7ueFZMULAkOjQJMuq0s5KuREEU3GAiA2mqZzSBAeNERFgE3dsAtTBkciozX+MTTE7U2oXXHxng=="}]},"_npmUser":{"name":"achneoder","email":"me@codingoncatnip.de"},"directories":{},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/l-fen_1.2.0-beta.4_1710924271101_0.7522766244732235"},"_hasShrinkwrap":false}},"time":{"created":"2022-10-27T14:08:36.874Z","1.0.0":"2022-10-27T14:08:37.155Z","modified":"2024-03-20T08:44:31.959Z","1.0.1":"2022-10-30T09:47:42.216Z","1.0.2":"2022-10-30T10:32:20.683Z","1.0.3":"2022-11-01T11:50:28.514Z","1.0.4":"2022-11-06T14:01:01.572Z","1.0.5":"2022-11-06T14:03:31.610Z","1.0.6":"2023-03-03T07:58:58.853Z","1.0.7":"2023-04-05T14:07:38.092Z","1.1.0-alpha.1":"2023-04-11T14:14:37.408Z","1.1.0-alpha.2":"2024-01-18T08:45:49.108Z","1.1.0-alpha.3":"2024-01-22T08:02:45.396Z","1.1.0-alpha.4":"2024-01-22T08:32:49.486Z","1.1.0-alpha.5":"2024-01-22T08:41:34.249Z","1.1.0-alpha.6":"2024-01-22T09:20:21.508Z","1.1.0-alpha.7":"2024-01-22T10:27:13.692Z","1.1.0-alpha.8":"2024-01-22T11:57:51.615Z","1.1.0-alpha.9":"2024-01-22T17:38:49.694Z","1.1.0-alpha.10":"2024-01-22T17:54:07.561Z","1.1.0-alpha.11":"2024-01-22T18:09:47.564Z","1.1.0-alpha.12":"2024-01-22T18:34:53.124Z","1.1.0-alpha.13":"2024-01-22T19:01:24.815Z","1.1.0-alpha.14":"2024-01-22T19:41:18.292Z","1.1.0-alpha.15":"2024-01-22T19:52:43.638Z","1.1.0-alpha.16":"2024-01-22T19:59:08.458Z","1.1.0-alpha.17":"2024-01-22T20:24:25.821Z","1.1.0-alpha.18":"2024-01-22T20:38:02.036Z","1.1.0-alpha.19":"2024-01-23T07:57:28.949Z","1.1.0":"2024-01-23T08:18:15.180Z","1.1.1":"2024-01-23T08:50:08.793Z","1.1.2":"2024-01-23T09:48:48.188Z","1.1.3":"2024-02-04T18:39:15.703Z","1.1.4":"2024-03-19T09:27:09.590Z","1.2.0-beta.1":"2024-03-19T12:03:55.088Z","1.2.0-beta.2":"2024-03-19T13:38:53.863Z","1.2.0-beta.3":"2024-03-19T13:49:33.850Z","1.2.0-beta.4":"2024-03-20T08:44:31.255Z"},"maintainers":[{"name":"achneoder","email":"me@codingoncatnip.de"}],"description":"sandbox to simulate full serverless function environment","homepage":"https://github.com/Achneoder/l-fen#readme","repository":{"type":"git","url":"git+https://github.com/Achneoder/l-fen.git"},"author":{"name":"me@codingoncatnip.de"},"bugs":{"url":"https://github.com/Achneoder/l-fen/issues"},"license":"MIT","readme":"","readmeFilename":""}