diff --git a/package-lock.json b/package-lock.json index 82d5fb20..ef73e749 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3276,6 +3276,247 @@ "node": ">=6.9.0" } }, + "node_modules/@bufbuild/buf": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf/-/buf-1.58.0.tgz", + "integrity": "sha512-/R+6kyZijftKDFLwY2JlvXqreZrIkz6jvcsmILXC0HwjkJ8dcADSPS93CFTZrtDQfui6K/GCJOsZrNbY5SLRyA==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "bin": { + "buf": "bin/buf", + "protoc-gen-buf-breaking": "bin/protoc-gen-buf-breaking", + "protoc-gen-buf-lint": "bin/protoc-gen-buf-lint" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@bufbuild/buf-darwin-arm64": "1.58.0", + "@bufbuild/buf-darwin-x64": "1.58.0", + "@bufbuild/buf-linux-aarch64": "1.58.0", + "@bufbuild/buf-linux-armv7": "1.58.0", + "@bufbuild/buf-linux-x64": "1.58.0", + "@bufbuild/buf-win32-arm64": "1.58.0", + "@bufbuild/buf-win32-x64": "1.58.0" + } + }, + "node_modules/@bufbuild/buf-darwin-arm64": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-arm64/-/buf-darwin-arm64-1.58.0.tgz", + "integrity": "sha512-bMlTG23f7oIrroVM7dijbCxwLy+fd4QOAkmnIkZ922UIuwXkexr8TWzrul4Ivs0Af6aOWNzQSyHrh3UkGNZa2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/buf-darwin-x64": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-darwin-x64/-/buf-darwin-x64-1.58.0.tgz", + "integrity": "sha512-sNOu4ta7IDaQi4F66BXk76AQVCr0H10Ic7UFfU9ELs1f+FP+JYsQRU5CrWeaDWnLUTu3o4EZqwC6AvhGLOJUnw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/buf-linux-aarch64": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-aarch64/-/buf-linux-aarch64-1.58.0.tgz", + "integrity": "sha512-UE3tmIBpA4tK4Y34602UAbCFJzZVuRrFoXys5qSu9LnqhP9OF+vT6x9SXpAlQigmq3VGwNr8wgxD17ys2oDEmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/buf-linux-armv7": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-armv7/-/buf-linux-armv7-1.58.0.tgz", + "integrity": "sha512-6V0bEseFAcNGP8IyHb1b3dEr/FpeuN6A/gHNotJ8zZbtyWsKEPsiSNomED8bARvW/3hs802khVJAUeD0duIpAw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/buf-linux-x64": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-linux-x64/-/buf-linux-x64-1.58.0.tgz", + "integrity": "sha512-k2xOlOky3IY9Zxeih5vccfp/MDfO0UPZfGYxkYJ7reNuUTtJEOWfzuQxeZY+E8q1W83RtIwGjAVhbKYCGA1MpQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/buf-win32-arm64": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-arm64/-/buf-win32-arm64-1.58.0.tgz", + "integrity": "sha512-GXNjYaOyjJ2J4hhCkldsc3r6eS1YqQ+qOHsn/PvtcxUkzV6UN5HoLoh0Bx/NStZOA4QqCEfQphLUqJLvwBuhbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/buf-win32-x64": { + "version": "1.58.0", + "resolved": "https://registry.npmjs.org/@bufbuild/buf-win32-x64/-/buf-win32-x64-1.58.0.tgz", + "integrity": "sha512-5o1d/7lEeDXuTyroiro+rRmFAbKIaKjVCFZwF0pPISUmIANFzvI41UpzciMQACXCSnYQdEcNHLtZRGCzGT9GiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@bufbuild/protobuf": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.9.0.tgz", + "integrity": "sha512-rnJenoStJ8nvmt9Gzye8nkYd6V22xUAnu4086ER7h1zJ508vStko4pMvDeQ446ilDTFpV5wnoc5YS7XvMwwMqA==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@bufbuild/protoc-gen-es": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protoc-gen-es/-/protoc-gen-es-2.9.0.tgz", + "integrity": "sha512-g54rrHLKc7fnxN25ikynstRxR19M5G5l/hyqut2JoypJ9iU9QgUE63zEm8PNvVfBd4r5PEzWPfbWy5MNOeuMKQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@bufbuild/protobuf": "2.9.0", + "@bufbuild/protoplugin": "2.9.0" + }, + "bin": { + "protoc-gen-es": "bin/protoc-gen-es" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@bufbuild/protobuf": "2.9.0" + }, + "peerDependenciesMeta": { + "@bufbuild/protobuf": { + "optional": true + } + } + }, + "node_modules/@bufbuild/protoplugin": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protoplugin/-/protoplugin-2.9.0.tgz", + "integrity": "sha512-uoiwNVYoTq+AyqaV1L6pBazGx5fXOO89L0NSR9/7hEfo0Y8n9T1jsKGu4mkitLmP3z+8gJREaule1mMuKBPyYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@bufbuild/protobuf": "2.9.0", + "@typescript/vfs": "^1.5.2", + "typescript": "5.4.5" + } + }, + "node_modules/@bufbuild/protoplugin/node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@connectrpc/connect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-2.1.0.tgz", + "integrity": "sha512-xhiwnYlJNHzmFsRw+iSPIwXR/xweTvTw8x5HiwWp10sbVtd4OpOXbRgE7V58xs1EC17fzusF1f5uOAy24OkBuA==", + "license": "Apache-2.0", + "peerDependencies": { + "@bufbuild/protobuf": "^2.7.0" + } + }, + "node_modules/@connectrpc/connect-fastify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@connectrpc/connect-fastify/-/connect-fastify-2.1.0.tgz", + "integrity": "sha512-5xPxUGQnawXgw0ZmgMGtEUN81aIJZOvgHw/j9USERffRDOUNcqMNcVCQ5ay+grc1XlkkC3AdQLWV2yctAzWg9g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@bufbuild/protobuf": "^2.7.0", + "@connectrpc/connect": "2.1.0", + "@connectrpc/connect-node": "2.1.0", + "fastify": "^4.22.1 || ^5.1.0" + } + }, + "node_modules/@connectrpc/connect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@connectrpc/connect-node/-/connect-node-2.1.0.tgz", + "integrity": "sha512-6akCXZSX5uWHLR654ne9Tnq7AnPUkLS65NvgsI5885xBkcuVy2APBd8sA4sLqaplUt84cVEr6LhjEFNx6W1KtQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@bufbuild/protobuf": "^2.7.0", + "@connectrpc/connect": "2.1.0" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -3358,10 +3599,11 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", - "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", + "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" @@ -3827,78 +4069,237 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@fastify/busboy": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.2.1.tgz", - "integrity": "sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q==", + "node_modules/@fastify/ajv-compiler": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.4.tgz", + "integrity": "sha512-VfPkOSmob5YqH4ZUYW4ESVV5dDNbmtNEKJADFm43Hn/T48RxTZjUIBouadRDb4M/qr8g5bAxxu40/MGxvCPDrw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", "dependencies": { - "text-decoding": "^1.0.0" - }, - "engines": { - "node": ">=14" + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0" } }, - "node_modules/@firebase/analytics": { - "version": "0.10.7", - "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.7.tgz", - "integrity": "sha512-GE29uTT6y/Jv2EP0OjpTezeTQZ5FTCTaZXKrrdVGjb/t35AU4u/jiU+hUwUPpuK8fqhhiHkS/AawE3a3ZK/a9Q==", - "dev": true, + "node_modules/@fastify/ajv-compiler/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", "dependencies": { - "@firebase/component": "0.6.8", - "@firebase/installations": "0.6.8", - "@firebase/logger": "0.4.2", - "@firebase/util": "1.9.7", - "tslib": "^2.1.0" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, - "peerDependencies": { - "@firebase/app": "0.x" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@firebase/analytics-compat": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.13.tgz", - "integrity": "sha512-aZ4wGfNDMsCxhKzDbK2g1aV0JKsdQ9FbeIsjpNJPzhahV0XYj+z36Y4RNLPpG/6hHU4gxnezxs+yn3HhHkNL8w==", - "dev": true, + "node_modules/@fastify/ajv-compiler/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", "dependencies": { - "@firebase/analytics": "0.10.7", - "@firebase/analytics-types": "0.8.2", - "@firebase/component": "0.6.8", - "@firebase/util": "1.9.7", - "tslib": "^2.1.0" + "ajv": "^8.0.0" }, "peerDependencies": { - "@firebase/app-compat": "0.x" + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "node_modules/@firebase/analytics-compat/node_modules/@firebase/component": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.8.tgz", - "integrity": "sha512-LcNvxGLLGjBwB0dJUsBGCej2fqAepWyBubs4jt1Tiuns7QLbXHuyObZ4aMeBjZjWx4m8g1LoVI9QFpSaq/k4/g==", - "dev": true, - "dependencies": { - "@firebase/util": "1.9.7", - "tslib": "^2.1.0" - } + "node_modules/@fastify/ajv-compiler/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" }, - "node_modules/@firebase/analytics-compat/node_modules/@firebase/util": { - "version": "1.9.7", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.7.tgz", - "integrity": "sha512-fBVNH/8bRbYjqlbIhZ+lBtdAAS4WqZumx03K06/u7fJSpz1TGjEMm1ImvKD47w+xaFKIP2ori6z8BrbakRfjJA==", - "dev": true, + "node_modules/@fastify/busboy": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.2.1.tgz", + "integrity": "sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q==", "dependencies": { - "tslib": "^2.1.0" + "text-decoding": "^1.0.0" + }, + "engines": { + "node": ">=14" } }, - "node_modules/@firebase/analytics-types": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.2.tgz", - "integrity": "sha512-EnzNNLh+9/sJsimsA/FGqzakmrAUKLeJvjRHlg8df1f97NLUlFidk9600y0ZgWOp3CAxn6Hjtk+08tixlUOWyw==", - "dev": true + "node_modules/@fastify/error": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz", + "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" }, - "node_modules/@firebase/analytics/node_modules/@firebase/component": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.8.tgz", - "integrity": "sha512-LcNvxGLLGjBwB0dJUsBGCej2fqAepWyBubs4jt1Tiuns7QLbXHuyObZ4aMeBjZjWx4m8g1LoVI9QFpSaq/k4/g==", + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.0.3.tgz", + "integrity": "sha512-uik7yYHkLr6fxd8hJSZ8c+xF4WafPK+XzneQDPU+D10r5X19GW8lJcom2YijX2+qtFF1ENJlHXKFM9ouXNJYgQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^6.0.0" + } + }, + "node_modules/@fastify/forwarded": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.1.tgz", + "integrity": "sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", + "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@fastify/proxy-addr": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.1.0.tgz", + "integrity": "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/forwarded": "^3.0.0", + "ipaddr.js": "^2.1.0" + } + }, + "node_modules/@fastify/proxy-addr/node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@firebase/analytics": { + "version": "0.10.7", + "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.7.tgz", + "integrity": "sha512-GE29uTT6y/Jv2EP0OjpTezeTQZ5FTCTaZXKrrdVGjb/t35AU4u/jiU+hUwUPpuK8fqhhiHkS/AawE3a3ZK/a9Q==", + "dev": true, + "dependencies": { + "@firebase/component": "0.6.8", + "@firebase/installations": "0.6.8", + "@firebase/logger": "0.4.2", + "@firebase/util": "1.9.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app": "0.x" + } + }, + "node_modules/@firebase/analytics-compat": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.13.tgz", + "integrity": "sha512-aZ4wGfNDMsCxhKzDbK2g1aV0JKsdQ9FbeIsjpNJPzhahV0XYj+z36Y4RNLPpG/6hHU4gxnezxs+yn3HhHkNL8w==", + "dev": true, + "dependencies": { + "@firebase/analytics": "0.10.7", + "@firebase/analytics-types": "0.8.2", + "@firebase/component": "0.6.8", + "@firebase/util": "1.9.7", + "tslib": "^2.1.0" + }, + "peerDependencies": { + "@firebase/app-compat": "0.x" + } + }, + "node_modules/@firebase/analytics-compat/node_modules/@firebase/component": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.8.tgz", + "integrity": "sha512-LcNvxGLLGjBwB0dJUsBGCej2fqAepWyBubs4jt1Tiuns7QLbXHuyObZ4aMeBjZjWx4m8g1LoVI9QFpSaq/k4/g==", + "dev": true, + "dependencies": { + "@firebase/util": "1.9.7", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/analytics-compat/node_modules/@firebase/util": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.7.tgz", + "integrity": "sha512-fBVNH/8bRbYjqlbIhZ+lBtdAAS4WqZumx03K06/u7fJSpz1TGjEMm1ImvKD47w+xaFKIP2ori6z8BrbakRfjJA==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/analytics-types": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.2.tgz", + "integrity": "sha512-EnzNNLh+9/sJsimsA/FGqzakmrAUKLeJvjRHlg8df1f97NLUlFidk9600y0ZgWOp3CAxn6Hjtk+08tixlUOWyw==", + "dev": true + }, + "node_modules/@firebase/analytics/node_modules/@firebase/component": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.8.tgz", + "integrity": "sha512-LcNvxGLLGjBwB0dJUsBGCej2fqAepWyBubs4jt1Tiuns7QLbXHuyObZ4aMeBjZjWx4m8g1LoVI9QFpSaq/k4/g==", "dev": true, "dependencies": { "@firebase/util": "1.9.7", @@ -5220,6 +5621,17 @@ "node": ">=6.9.0" } }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, "node_modules/@img/sharp-darwin-arm64": { "version": "0.33.5", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", @@ -5328,6 +5740,23 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.3.tgz", + "integrity": "sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@img/sharp-libvips-linux-s390x": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", @@ -5436,6 +5865,29 @@ "@img/sharp-libvips-linux-arm64": "1.0.4" } }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.4.tgz", + "integrity": "sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.3" + } + }, "node_modules/@img/sharp-linux-s390x": { "version": "0.33.5", "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", @@ -5543,6 +5995,26 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.4.tgz", + "integrity": "sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@img/sharp-win32-ia32": { "version": "0.33.5", "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", @@ -6052,6 +6524,16 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/@jsdoc/salty": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.7.tgz", @@ -6290,205 +6772,55 @@ "node": ">= 0.4" } }, - "node_modules/@next/env": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.4.tgz", - "integrity": "sha512-irQnbMLbUNQpP1wcE5NstJtbuA/69kRfzBrpAD7Gsn8zm/CY6YQYc3HQBz8QPxwISG26tIm5afvvVbu508oBeQ==", - "dev": true - }, - "node_modules/@next/swc-darwin-arm64": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.4.tgz", - "integrity": "sha512-mF05E/5uPthWzyYDyptcwHptucf/jj09i2SXBPwNzbgBNc+XnwzrL0U6BmPjQeOL+FiB+iG1gwBeq7mlDjSRPg==", - "cpu": [ - "arm64" - ], + "node_modules/@ngtools/webpack": { + "version": "17.1.2", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-17.1.2.tgz", + "integrity": "sha512-MdNVSIp0x8AK26L+CxMTXH4weq2sNIp4C09RSdk7y6UkfBxMA3O0jTto9tW3ehkBaaGZ4dSiWkXA8L/ydMiQmA==", "dev": true, - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": ">= 10" + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^17.0.0", + "typescript": ">=5.2 <5.4", + "webpack": "^5.54.0" } }, - "node_modules/@next/swc-darwin-x64": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.4.tgz", - "integrity": "sha512-IZQ3C7Bx0k2rYtrZZxKKiusMTM9WWcK5ajyhOZkYYTCc8xytmwSzR1skU7qLgVT/EY9xtXDG0WhY6fyujnI3rw==", - "cpu": [ - "x64" - ], + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, "engines": { - "node": ">= 10" + "node": ">= 8" } }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.4.tgz", - "integrity": "sha512-VwwZKrBQo/MGb1VOrxJ6LrKvbpo7UbROuyMRvQKTFKhNaXjUmKTu7wxVkIuCARAfiI8JpaWAnKR+D6tzpCcM4w==", - "cpu": [ - "arm64" - ], + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">= 10" + "node": ">= 8" } }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.4.tgz", - "integrity": "sha512-8QftwPEW37XxXoAwsn+nXlodKWHfpMaSvt81W43Wh8dv0gkheD+30ezWMcFGHLI71KiWmHK5PSQbTQGUiidvLQ==", - "cpu": [ - "arm64" - ], + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.4.tgz", - "integrity": "sha512-/s/Pme3VKfZAfISlYVq2hzFS8AcAIOTnoKupc/j4WlvF6GQ0VouS2Q2KEgPuO1eMBwakWPB1aYFIA4VNVh667A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.4.tgz", - "integrity": "sha512-m8z/6Fyal4L9Bnlxde5g2Mfa1Z7dasMQyhEhskDATpqr+Y0mjOBZcXQ7G5U+vgL22cI4T7MfvgtrM2jdopqWaw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.4.tgz", - "integrity": "sha512-7Wv4PRiWIAWbm5XrGz3D8HUkCVDMMz9igffZG4NB1p4u1KoItwx9qjATHz88kwCEal/HXmbShucaslXCQXUM5w==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.4.tgz", - "integrity": "sha512-zLeNEAPULsl0phfGb4kdzF/cAVIfaC7hY+kt0/d+y9mzcZHsMS3hAS829WbJ31DkSlVKQeHEjZHIdhN+Pg7Gyg==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.4.tgz", - "integrity": "sha512-yEh2+R8qDlDCjxVpzOTEpBLQTEFAcP2A8fUFLaWNap9GitYKkKv1//y2S6XY6zsR4rCOPRpU7plYDR+az2n30A==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@ngtools/webpack": { - "version": "17.1.2", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-17.1.2.tgz", - "integrity": "sha512-MdNVSIp0x8AK26L+CxMTXH4weq2sNIp4C09RSdk7y6UkfBxMA3O0jTto9tW3ehkBaaGZ4dSiWkXA8L/ydMiQmA==", - "dev": true, - "engines": { - "node": "^18.13.0 || >=20.9.0", - "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", - "yarn": ">= 1.13.0" - }, - "peerDependencies": { - "@angular/compiler-cli": "^17.0.0", - "typescript": ">=5.2 <5.4", - "webpack": "^5.54.0" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" + "node": ">= 8" } }, "node_modules/@npm/types": { @@ -9269,15 +9601,6 @@ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", "dev": true }, - "node_modules/@swc/helpers": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", - "integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==", - "dev": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -10002,6 +10325,19 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript/vfs": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@typescript/vfs/-/vfs-1.6.1.tgz", + "integrity": "sha512-JwoxboBh7Oz1v38tPbkrZ62ZXNHAk9bJ7c9x0eI5zBfBnBYGhURdbnh7Z4smN/MV48Y5OCcZb58n972UtbazsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1" + }, + "peerDependencies": { + "typescript": "*" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -10829,6 +11165,12 @@ "node": ">=6.5" } }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "license": "MIT" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -11230,7 +11572,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", - "dev": true, "engines": { "node": ">=8.0.0" } @@ -11272,6 +11613,16 @@ "postcss": "^8.1.0" } }, + "node_modules/avvio": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.1.0.tgz", + "integrity": "sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==", + "license": "MIT", + "dependencies": { + "@fastify/error": "^4.0.0", + "fastq": "^1.17.1" + } + }, "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -11947,6 +12298,19 @@ } ] }, + "node_modules/case-anything": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/case-anything/-/case-anything-2.1.13.tgz", + "integrity": "sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -12130,7 +12494,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "devOptional": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -12144,7 +12507,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "devOptional": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -13186,8 +13548,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, - "peer": true, "engines": { "node": ">=6" } @@ -13211,10 +13571,11 @@ } }, "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=8" } @@ -13385,6 +13746,29 @@ "node": ">=12" } }, + "node_modules/dprint-node": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/dprint-node/-/dprint-node-1.0.8.tgz", + "integrity": "sha512-iVKnUtYfGrYcW1ZAlfR/F59cUVL8QIhWoBJoSjkkdua/dkWIgjZfiLMeTjiB06X0ZLkQ0M2C1VbUj/CxkIf1zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^1.0.3" + } + }, + "node_modules/dprint-node/node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -13670,7 +14054,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "devOptional": true, "engines": { "node": ">=6" } @@ -14200,11 +14583,16 @@ "node >=0.6.0" ] }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "devOptional": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-diff": { "version": "1.3.0", @@ -14252,12 +14640,84 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, + "node_modules/fast-json-stringify": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.1.1.tgz", + "integrity": "sha512-DbgptncYEXZqDUOEl4krff4mUiVrTZZVI7BBrQR/T3BqMj/eM1flTC1Uk2uUoLcWCxjT95xKulV/Lc6hhOZsBQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.2.0", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0", + "json-schema-ref-resolver": "^3.0.0", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-json-stringify/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/fast-json-stringify/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/fast-json-stringify/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "devOptional": true }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, "node_modules/fast-redact": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", @@ -14279,6 +14739,22 @@ "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==", "optional": true }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fast-xml-parser": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", @@ -14301,37 +14777,162 @@ "fxparser": "src/cli/cli.js" } }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, + "node_modules/fastify": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.6.1.tgz", + "integrity": "sha512-WjjlOciBF0K8pDUPZoGPhqhKrQJ02I8DKaDIfO51EL0kbSMwQFl85cRwhOvmSDWoukNOdTo27gLN549pLCcH7Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "@fastify/ajv-compiler": "^4.0.0", + "@fastify/error": "^4.0.0", + "@fastify/fast-json-stringify-compiler": "^5.0.0", + "@fastify/proxy-addr": "^5.0.0", + "abstract-logging": "^2.0.1", + "avvio": "^9.0.0", + "fast-json-stringify": "^6.0.0", + "find-my-way": "^9.0.0", + "light-my-request": "^6.0.0", + "pino": "^9.0.0", + "process-warning": "^5.0.0", + "rfdc": "^1.3.1", + "secure-json-parse": "^4.0.0", + "semver": "^7.6.0", + "toad-cache": "^3.7.0" + } + }, + "node_modules/fastify/node_modules/pino": { + "version": "9.13.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.13.1.tgz", + "integrity": "sha512-Szuj+ViDTjKPQYiKumGmEn3frdl+ZPSdosHyt9SnUevFosOkMY2b7ipxlEctNKPmMD/VibeBI+ZcZCJK+4DPuw==", + "license": "MIT", "dependencies": { - "websocket-driver": ">=0.5.1" + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "slow-redact": "^0.3.0", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" }, - "engines": { - "node": ">=0.8.0" + "bin": { + "pino": "bin.js" } }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, + "node_modules/fastify/node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, + "split2": "^4.0.0" + } + }, + "node_modules/fastify/node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", + "license": "MIT" + }, + "node_modules/fastify/node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/fastify/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fastify/node_modules/sonic-boom": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", + "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/fastify/node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/fastify/node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -14542,6 +15143,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/find-my-way": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.3.0.tgz", + "integrity": "sha512-eRoFWQw+Yv2tuYlK2pjFS2jGXSxSppAs3hSQjfxVKxM5amECzIgYYc1FEI8ZmhSh/Ig+FrKEz43NLRKJjYCZVg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -14968,7 +15583,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "devOptional": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -16623,6 +17237,25 @@ "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", "dev": true }, + "node_modules/json-schema-ref-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz", + "integrity": "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -17349,6 +17982,52 @@ } } }, + "node_modules/light-my-request": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz", + "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "dependencies": { + "cookie": "^1.0.1", + "process-warning": "^4.0.0", + "set-cookie-parser": "^2.6.0" + } + }, + "node_modules/light-my-request/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/light-my-request/node_modules/process-warning": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", + "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/limiter": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", @@ -17460,8 +18139,7 @@ "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "devOptional": true + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" }, "node_modules/lodash.clonedeep": { "version": "4.5.0", @@ -18493,53 +19171,6 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, - "node_modules/next": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/next/-/next-14.0.4.tgz", - "integrity": "sha512-qbwypnM7327SadwFtxXnQdGiKpkuhaRLE2uq62/nRul9cj9KhQ5LhHmlziTNqUidZotw/Q1I9OjirBROdUJNgA==", - "dev": true, - "dependencies": { - "@next/env": "14.0.4", - "@swc/helpers": "0.5.2", - "busboy": "1.6.0", - "caniuse-lite": "^1.0.30001406", - "graceful-fs": "^4.2.11", - "postcss": "8.4.31", - "styled-jsx": "5.1.1", - "watchpack": "2.4.0" - }, - "bin": { - "next": "dist/bin/next" - }, - "engines": { - "node": ">=18.17.0" - }, - "optionalDependencies": { - "@next/swc-darwin-arm64": "14.0.4", - "@next/swc-darwin-x64": "14.0.4", - "@next/swc-linux-arm64-gnu": "14.0.4", - "@next/swc-linux-arm64-musl": "14.0.4", - "@next/swc-linux-x64-gnu": "14.0.4", - "@next/swc-linux-x64-musl": "14.0.4", - "@next/swc-win32-arm64-msvc": "14.0.4", - "@next/swc-win32-ia32-msvc": "14.0.4", - "@next/swc-win32-x64-msvc": "14.0.4" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.1.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "sass": "^1.3.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "sass": { - "optional": true - } - } - }, "node_modules/nice-napi": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", @@ -19512,7 +20143,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", - "dev": true, "engines": { "node": ">=14.0.0" } @@ -20869,10 +21499,11 @@ } }, "node_modules/protobufjs": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.5.tgz", - "integrity": "sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", "hasInstallScript": true, + "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -20959,6 +21590,19 @@ "node": ">=10" } }, + "node_modules/protoc": { + "version": "32.1.0", + "resolved": "https://registry.npmjs.org/protoc/-/protoc-32.1.0.tgz", + "integrity": "sha512-yICJJCGHJLM9ao5W2V4CGp1d7xuBsdHzgVDw6L8mdDtoIcqzN3arNPOm9Jx4Ufp5vfhQfJGkNUDo6mm/SLPdXw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "protoc": "protoc.cjs" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/protocols": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/protocols/-/protocols-2.0.1.tgz", @@ -21123,8 +21767,7 @@ "node_modules/quick-format-unescaped": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", - "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", - "dev": true + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" }, "node_modules/quick-lru": { "version": "4.0.1", @@ -21572,7 +22215,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", - "dev": true, "engines": { "node": ">= 12.13.0" } @@ -21678,7 +22320,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -21687,7 +22328,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -21797,6 +22437,15 @@ "node": ">=8" } }, + "node_modules/ret": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -21823,12 +22472,17 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -21948,11 +22602,29 @@ } ] }, + "node_modules/safe-regex2": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.0.0.tgz", + "integrity": "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ret": "~0.5.0" + } + }, "node_modules/safe-stable-stringify": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", - "dev": true, "engines": { "node": ">=10" } @@ -22086,6 +22758,22 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, + "node_modules/secure-json-parse": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -22280,8 +22968,7 @@ "node_modules/set-cookie-parser": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.0.tgz", - "integrity": "sha512-lXLOiqpkUumhRdFF3k1osNXCy9akgx/dyPZ5p8qAg9seJzXr5ZrlqZuWIMuY6ejOsVLE6flJ5/h3lsn57fQ/PQ==", - "dev": true + "integrity": "sha512-lXLOiqpkUumhRdFF3k1osNXCy9akgx/dyPZ5p8qAg9seJzXr5ZrlqZuWIMuY6ejOsVLE6flJ5/h3lsn57fQ/PQ==" }, "node_modules/set-function-length": { "version": "1.2.2", @@ -22709,6 +23396,12 @@ "node": ">=8" } }, + "node_modules/slow-redact": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/slow-redact/-/slow-redact-0.3.2.tgz", + "integrity": "sha512-MseHyi2+E/hBRqdOi5COy6wZ7j7DxXRz9NkseavNYSvvWC06D8a5cidVZX3tcG5eCW3NIyVU4zT63hw0Q486jw==", + "license": "MIT" + }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -23173,29 +23866,6 @@ "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", "optional": true }, - "node_modules/styled-jsx": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", - "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", - "dev": true, - "dependencies": { - "client-only": "0.0.1" - }, - "engines": { - "node": ">= 12.0.0" - }, - "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "babel-plugin-macros": { - "optional": true - } - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -23583,6 +24253,15 @@ "node": ">=8.0" } }, + "node_modules/toad-cache": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", + "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -23846,6 +24525,42 @@ "node": ">=0.3.1" } }, + "node_modules/ts-poet": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/ts-poet/-/ts-poet-6.12.0.tgz", + "integrity": "sha512-xo+iRNMWqyvXpFTaOAvLPA5QAWO6TZrSUs5s4Odaya3epqofBu/fMLHEWl8jPmjhA0s9sgj9sNvF1BmaQlmQkA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dprint-node": "^1.0.8" + } + }, + "node_modules/ts-proto": { + "version": "2.7.7", + "resolved": "https://registry.npmjs.org/ts-proto/-/ts-proto-2.7.7.tgz", + "integrity": "sha512-/OfN9/Yriji2bbpOysZ/Jzc96isOKz+eBTJEcKaIZ0PR6x1TNgVm4Lz0zfbo+J0jwFO7fJjJyssefBPQ0o1V9A==", + "dev": true, + "license": "ISC", + "dependencies": { + "@bufbuild/protobuf": "^2.0.0", + "case-anything": "^2.1.13", + "ts-poet": "^6.12.0", + "ts-proto-descriptors": "2.0.0" + }, + "bin": { + "protoc-gen-ts_proto": "protoc-gen-ts_proto" + } + }, + "node_modules/ts-proto-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-proto-descriptors/-/ts-proto-descriptors-2.0.0.tgz", + "integrity": "sha512-wHcTH3xIv11jxgkX5OyCSFfw27agpInAd6yh89hKG6zqIXnjW9SYqSER2CVQxdPj4czeOhGagNvZBEbJPy7qkw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@bufbuild/protobuf": "^2.0.0" + } + }, "node_modules/tsconfig-paths": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", @@ -25322,6 +26037,16 @@ } } }, + "node_modules/xhr2": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/xhr2/-/xhr2-0.2.1.tgz", + "integrity": "sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/xmlcreate": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", @@ -25341,7 +26066,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "devOptional": true, "engines": { "node": ">=10" } @@ -25617,27 +26341,39 @@ } }, "packages/@apphosting/adapter-nextjs": { - "version": "14.0.18", + "version": "15.0.0", "license": "Apache-2.0", "dependencies": { "@apphosting/common": "*", + "@connectrpc/connect": "^2.1.0", + "@grpc/grpc-js": "^1.14.0", + "fastify": "^5.6.1", "fs-extra": "^11.1.1", "yaml": "^2.3.4" }, "bin": { "apphosting-adapter-nextjs-build": "dist/bin/build.js", - "apphosting-adapter-nextjs-create": "dist/bin/create.js" + "apphosting-adapter-nextjs-create": "dist/bin/create.js", + "apphosting-adapter-nextjs-serve": "dist/bin/serve.js" }, "devDependencies": { + "@angular/platform-server": "^20.3.6", + "@angular/ssr": "^20.3.6", + "@bufbuild/buf": "^1.58.0", + "@bufbuild/protobuf": "^2.9.0", + "@bufbuild/protoc-gen-es": "^2.9.0", + "@connectrpc/connect-fastify": "^2.1.0", "@types/fs-extra": "*", "@types/mocha": "*", "@types/tmp": "*", "mocha": "*", - "next": "~14.0.0", + "next": "15.6.0-canary.54", + "protoc": "^32.1.0", "semver": "*", "tmp": "*", "ts-mocha": "*", "ts-node": "*", + "ts-proto": "^2.7.7", "typescript": "*", "verdaccio": "^5.30.3" }, @@ -25650,13 +26386,934 @@ } } }, - "packages/@apphosting/build": { - "version": "0.1.6", + "packages/@apphosting/adapter-nextjs-exp": { + "version": "15.0.0", + "extraneous": true, "license": "Apache-2.0", "dependencies": { "@apphosting/common": "*", - "@npmcli/promise-spawn": "^3.0.0", - "colorette": "^2.0.20", + "fs-extra": "^11.1.1", + "yaml": "^2.3.4" + }, + "bin": { + "apphosting-adapter-nextjs-build": "dist/bin/build.js", + "apphosting-adapter-nextjs-create": "dist/bin/create.js" + }, + "devDependencies": { + "@types/fs-extra": "*", + "@types/mocha": "*", + "@types/tmp": "*", + "mocha": "*", + "next": "~14.0.0", + "semver": "*", + "tmp": "*", + "ts-mocha": "*", + "ts-node": "*", + "typescript": "*", + "verdaccio": "^5.30.3" + }, + "peerDependencies": { + "next": "*" + }, + "peerDependenciesMeta": { + "next": { + "optional": true + } + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@angular/common": { + "version": "20.3.6", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.6.tgz", + "integrity": "sha512-+gHMuFe0wz4f+vfGZ2q+fSQSYaY7KlN7QdDrFqLnA7H2sythzhXvRbXEtp4DkPjihh9gupXg2MeLh1ROy5AfSw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/core": "20.3.6", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@angular/compiler": { + "version": "20.3.6", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.6.tgz", + "integrity": "sha512-OdjXBsAsnn7qiW6fSHClwn9XwjVxhtO9+RbDc6Mf+YPCnJq0s8T78H2fc8VdJFp/Rs+tMZcwwjd9VZPm8+2XWA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@angular/core": { + "version": "20.3.6", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.6.tgz", + "integrity": "sha512-sDURQWnjwE4Y750u/5qwkZEYMoI4CrKghnx4aKulxCnohR3//C78wvz6p8MtCuqYfzGkdQZDYFg8tgAz17qgPw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/compiler": "20.3.6", + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.15.0" + }, + "peerDependenciesMeta": { + "@angular/compiler": { + "optional": true + }, + "zone.js": { + "optional": true + } + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@angular/platform-browser": { + "version": "20.3.6", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.6.tgz", + "integrity": "sha512-gFp1yd+HtRN8XdpMatRLO5w6FLIzsnF31lD2Duo4BUTCoMAMdfaNT6FtcvNdKu7ANo27Ke26fxEEE2bh6FU98A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/animations": "20.3.6", + "@angular/common": "20.3.6", + "@angular/core": "20.3.6" + }, + "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + } + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@angular/platform-server": { + "version": "20.3.6", + "resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-20.3.6.tgz", + "integrity": "sha512-fWF20pZYt8+4ZbNEwQsSgvBc11g8QWiVW7a0ybPvn7fy4LsTLWPzpolGK54k3FqWTQsZfzt+tVcNS709FPETfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0", + "xhr2": "^0.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/common": "20.3.6", + "@angular/compiler": "20.3.6", + "@angular/core": "20.3.6", + "@angular/platform-browser": "20.3.6", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@angular/router": { + "version": "20.3.6", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.6.tgz", + "integrity": "sha512-fSAYOR9nKpH5PoBYFNdII3nAFl2maUrYiISU33CnGwb7J7Q0s09k231c/P5tVN4URi+jdADVwiBI8cIYk8SVrg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@angular/common": "20.3.6", + "@angular/core": "20.3.6", + "@angular/platform-browser": "20.3.6", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@angular/ssr": { + "version": "20.3.6", + "resolved": "https://registry.npmjs.org/@angular/ssr/-/ssr-20.3.6.tgz", + "integrity": "sha512-YABzCGfjwQ+WSUuInwBGS35pNO2zUHJeCzyj6CZ0sqRVR0qt3KvEzk1RVvR33X1bKePFHurn8NYPkcvLSpBvhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^20.0.0", + "@angular/core": "^20.0.0", + "@angular/platform-server": "^20.0.0", + "@angular/router": "^20.0.0" + }, + "peerDependenciesMeta": { + "@angular/platform-server": { + "optional": true + } + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@grpc/grpc-js": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.0.tgz", + "integrity": "sha512-N8Jx6PaYzcTRNzirReJCtADVoq4z7+1KQ4E70jTg/koQiMoUSN1kbNjPOqpPbhMFhfU1/l7ixspPl8dNY+FoUg==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.8.0", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@grpc/proto-loader": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", + "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.5.3", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.4.tgz", + "integrity": "sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.3" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@img/sharp-darwin-x64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.4.tgz", + "integrity": "sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.3" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.3.tgz", + "integrity": "sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.3.tgz", + "integrity": "sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.3.tgz", + "integrity": "sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.3.tgz", + "integrity": "sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.3.tgz", + "integrity": "sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.3.tgz", + "integrity": "sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.3.tgz", + "integrity": "sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.3.tgz", + "integrity": "sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@img/sharp-linux-arm": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.4.tgz", + "integrity": "sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.3" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@img/sharp-linux-arm64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.4.tgz", + "integrity": "sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.3" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@img/sharp-linux-s390x": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.4.tgz", + "integrity": "sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.3" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@img/sharp-linux-x64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.4.tgz", + "integrity": "sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.3" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.4.tgz", + "integrity": "sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.3" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.4.tgz", + "integrity": "sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.3" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@img/sharp-wasm32": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.4.tgz", + "integrity": "sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.5.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@img/sharp-win32-ia32": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.4.tgz", + "integrity": "sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@img/sharp-win32-x64": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.4.tgz", + "integrity": "sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@next/env": { + "version": "15.6.0-canary.54", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.6.0-canary.54.tgz", + "integrity": "sha512-JgxtLpWVwu7iAC2oKmXSEZlAn7ogsPVWqBVGftwj9QpP0ufHASbt3UTHBXo2Fc4/Msl8PYeL19DlahcRfUDwaQ==", + "dev": true, + "license": "MIT" + }, + "packages/@apphosting/adapter-nextjs/node_modules/@next/swc-darwin-arm64": { + "version": "15.6.0-canary.54", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.6.0-canary.54.tgz", + "integrity": "sha512-B+QjESWfG3LdzQ9FhpmIrJQdYmJP9VhulKxaYUAimA2T+E+qxTk+IfeVkwqOmqWcubZeVBOLiREE+VOPXZtnAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@next/swc-darwin-x64": { + "version": "15.6.0-canary.54", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.6.0-canary.54.tgz", + "integrity": "sha512-loW60NZS+xvItpdMRBCF1N0JJ98JmDmEH1jDsqO0VzQsrgS3cNnbVslnC6N3+7ndMDN+reasYA9gfkvFVn1Aww==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.6.0-canary.54", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.6.0-canary.54.tgz", + "integrity": "sha512-p66oPQzprGvp1TEvtNFuEiWxfflHOsvfJBfVJpOefmwnwsHbys2wSeA2tJD2xk8yzk5+ChT1X168atpKBdMKgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@next/swc-linux-arm64-musl": { + "version": "15.6.0-canary.54", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.6.0-canary.54.tgz", + "integrity": "sha512-JER2Y58ywlAQTa3eds3p6tNu9ts7nwVf31XBoA59yNIwQ1sgAm4+MGhs257FS/AW7FRclbSJw+FspEaq1SxbOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@next/swc-linux-x64-gnu": { + "version": "15.6.0-canary.54", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.6.0-canary.54.tgz", + "integrity": "sha512-lyc7WPq8l7wNS5l22ylDBcZw4m0+3kWKVuCQEL4u8RVHVkqds2XiW6T8hrdKMoHUynBJQxn0OnUvLTT7+1NCpQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@next/swc-linux-x64-musl": { + "version": "15.6.0-canary.54", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.6.0-canary.54.tgz", + "integrity": "sha512-NKa4/hFvHpSq7hvCzrnRwXyfj3PN7/Cquaz8aAlELfzEc+gGDUg2KjXwhEqtf4Br4PppLlDJSJZBr5hnCKJkLA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.6.0-canary.54", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.6.0-canary.54.tgz", + "integrity": "sha512-Zgib659a4e1fyunnyvP2WPClJei+riSVElY6ON5kNKu9sxVJNRHg6NidaJvJlv0kC+Twxwvrr9NF2h76hLpXsQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@next/swc-win32-x64-msvc": { + "version": "15.6.0-canary.54", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.6.0-canary.54.tgz", + "integrity": "sha512-/17K8vkgT5H4RbNFL6clVdtVnz5W/xLj1194uDF6ThiVfXG9Ml4mG3Jitiz2TUYS9uhAcwyhbCuqqR2/FcvFvg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/next": { + "version": "15.6.0-canary.54", + "resolved": "https://registry.npmjs.org/next/-/next-15.6.0-canary.54.tgz", + "integrity": "sha512-y7nLDe600/T6rbAz5JjQOCSHUzn9vm0t+OXy7F8qv+ZHCSaMj/hXgOiu9i4Z9O59v+rUrNV8n/vXi5yxgm7ZWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/env": "15.6.0-canary.54", + "@swc/helpers": "0.5.15", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "15.6.0-canary.54", + "@next/swc-darwin-x64": "15.6.0-canary.54", + "@next/swc-linux-arm64-gnu": "15.6.0-canary.54", + "@next/swc-linux-arm64-musl": "15.6.0-canary.54", + "@next/swc-linux-x64-gnu": "15.6.0-canary.54", + "@next/swc-linux-x64-musl": "15.6.0-canary.54", + "@next/swc-win32-arm64-msvc": "15.6.0-canary.54", + "@next/swc-win32-x64-msvc": "15.6.0-canary.54", + "sharp": "^0.34.4" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/sharp": { + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.4.tgz", + "integrity": "sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.0", + "semver": "^7.7.2" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.4", + "@img/sharp-darwin-x64": "0.34.4", + "@img/sharp-libvips-darwin-arm64": "1.2.3", + "@img/sharp-libvips-darwin-x64": "1.2.3", + "@img/sharp-libvips-linux-arm": "1.2.3", + "@img/sharp-libvips-linux-arm64": "1.2.3", + "@img/sharp-libvips-linux-ppc64": "1.2.3", + "@img/sharp-libvips-linux-s390x": "1.2.3", + "@img/sharp-libvips-linux-x64": "1.2.3", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.3", + "@img/sharp-libvips-linuxmusl-x64": "1.2.3", + "@img/sharp-linux-arm": "0.34.4", + "@img/sharp-linux-arm64": "0.34.4", + "@img/sharp-linux-ppc64": "0.34.4", + "@img/sharp-linux-s390x": "0.34.4", + "@img/sharp-linux-x64": "0.34.4", + "@img/sharp-linuxmusl-arm64": "0.34.4", + "@img/sharp-linuxmusl-x64": "0.34.4", + "@img/sharp-wasm32": "0.34.4", + "@img/sharp-win32-arm64": "0.34.4", + "@img/sharp-win32-ia32": "0.34.4", + "@img/sharp-win32-x64": "0.34.4" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "packages/@apphosting/adapter-nextjs/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "packages/@apphosting/adapter-nextjs/node_modules/zone.js": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.15.1.tgz", + "integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "packages/@apphosting/build": { + "version": "0.1.6", + "license": "Apache-2.0", + "dependencies": { + "@apphosting/common": "*", + "@npmcli/promise-spawn": "^3.0.0", + "colorette": "^2.0.20", "commander": "^11.1.0", "npm-pick-manifest": "^9.0.0", "ts-node": "^10.9.1" @@ -25753,7 +27410,7 @@ } }, "packages/@apphosting/common": { - "version": "0.0.8", + "version": "0.0.9", "license": "Apache-2.0" }, "packages/@apphosting/create": { diff --git a/packages/@apphosting/adapter-nextjs/.gitignore b/packages/@apphosting/adapter-nextjs/.gitignore index e69de29b..3ab5a3c0 100644 --- a/packages/@apphosting/adapter-nextjs/.gitignore +++ b/packages/@apphosting/adapter-nextjs/.gitignore @@ -0,0 +1 @@ +src/protos \ No newline at end of file diff --git a/packages/@apphosting/adapter-nextjs/buf.gen.yaml b/packages/@apphosting/adapter-nextjs/buf.gen.yaml new file mode 100644 index 00000000..23641671 --- /dev/null +++ b/packages/@apphosting/adapter-nextjs/buf.gen.yaml @@ -0,0 +1,19 @@ +# Learn more: https://buf.build/docs/configuration/v2/buf-gen-yaml +version: v2 +inputs: + - module: buf.build/envoyproxy/envoy:main + types: + - "envoy.service.ext_proc.v3.ExternalProcessor" + - module: buf.build/cncf/xds:main + types: + - "udpa.annotations.VersioningAnnotation" + - "udpa.annotations.FileMigrateAnnotation" + - "udpa.annotations.StatusAnnotation" + - "xds.annotations.v3.StatusAnnotation" + - module: buf.build/envoyproxy/protoc-gen-validate:main + paths: + - "validate/validate.proto" +plugins: + - local: protoc-gen-es + opt: target=ts + out: src/protos \ No newline at end of file diff --git a/packages/@apphosting/adapter-nextjs/clean_protos.cjs b/packages/@apphosting/adapter-nextjs/clean_protos.cjs new file mode 100644 index 00000000..576845ac --- /dev/null +++ b/packages/@apphosting/adapter-nextjs/clean_protos.cjs @@ -0,0 +1,62 @@ +const fs = require('fs'); +const path = require('path'); + +const TARGET_DIR = './src/protos'; + +// This regex finds lines with relative imports. +// It captures three groups: +// 1. The part before the path (e.g., `from '`) +// 2. The relative path itself (e.g., `./some/file`) +// 3. The closing quote (e.g., `'`) +const IMPORT_REGEX = /(from\s+['"])(\.\.?\/[^'"]+)(['"])/g; + +/** + * Recursively walks a directory and applies a file processing function. + * @param {string} directory The directory to walk. + * @param {(filePath: string) => void} processFile The function to apply to each file. + */ +function walkDirectory(directory, processFile) { + const items = fs.readdirSync(directory); + for (const item of items) { + const fullPath = path.join(directory, item); + const stat = fs.statSync(fullPath); + if (stat.isDirectory()) { + walkDirectory(fullPath, processFile); + } else if (fullPath.endsWith('.ts')) { + processFile(fullPath); + } + } +} + +/** + * Reads a TypeScript file, adds '.js' extensions to relative imports, + * and writes the changes back to the file. + * @param {string} filePath The path to the TypeScript file. + */ +function addJsExtensions(filePath) { + let content = fs.readFileSync(filePath, 'utf8'); + let changesMade = false; + + const newContent = content.replace(IMPORT_REGEX, (match, pre, importPath, post) => { + // Check if the path already has an extension. + // path.extname() returns the extension (e.g., '.js') or an empty string. + if (path.extname(importPath)) { + return match; // No change needed + } + + changesMade = true; + const newImportPath = `${importPath}.js`; + return `${pre}${newImportPath}${post}`; + }); + + if (changesMade) { + fs.writeFileSync(filePath, newContent, 'utf8'); + } +} + +try { + walkDirectory(TARGET_DIR, addJsExtensions); +} catch (error) { + console.error(`Error processing files: ${error.message}`); + process.exit(1); +} diff --git a/packages/@apphosting/adapter-nextjs/package.json b/packages/@apphosting/adapter-nextjs/package.json index 3490ab47..1886ba02 100644 --- a/packages/@apphosting/adapter-nextjs/package.json +++ b/packages/@apphosting/adapter-nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@apphosting/adapter-nextjs", - "version": "14.0.18", + "version": "15.0.0", "main": "dist/index.js", "description": "Experimental addon to the Firebase CLI to add web framework support", "repository": { @@ -9,6 +9,7 @@ }, "bin": { "apphosting-adapter-nextjs-build": "dist/bin/build.js", + "apphosting-adapter-nextjs-serve": "dist/bin/serve.js", "apphosting-adapter-nextjs-create": "dist/bin/create.js" }, "author": { @@ -21,7 +22,8 @@ "type": "module", "sideEffects": false, "scripts": { - "build": "rm -rf dist && tsc && chmod +x ./dist/bin/*", + "build:protos": "npx buf generate && node ./clean_protos.cjs", + "build": "npm run build:protos && rm -rf dist && tsc && chmod +x ./dist/bin/* && npx -y esbuild ./dist/index.js --bundle --format=cjs --platform=node --outfile=./dist/index.cjs && npx -y esbuild ./src/bin/serve.ts --bundle --format=esm --platform=node --outfile=./dist/bin/serve.js --external:fastify --external:next", "test": "npm run test:unit && npm run test:functional", "test:unit": "ts-mocha -p tsconfig.json 'src/**/*.spec.ts' 'src/*.spec.ts'", "test:functional": "node --loader ts-node/esm ./e2e/run-local.ts", @@ -30,7 +32,7 @@ }, "exports": { ".": { - "node": "./dist/index.js", + "node": "./dist/index.cjs", "default": null }, "./dist/*": { @@ -44,6 +46,7 @@ "license": "Apache-2.0", "dependencies": { "@apphosting/common": "*", + "fastify": "^5.6.1", "fs-extra": "^11.1.1", "yaml": "^2.3.4" }, @@ -56,15 +59,22 @@ } }, "devDependencies": { + "@connectrpc/connect": "^2.1.0", + "@bufbuild/buf": "^1.58.0", + "@bufbuild/protobuf": "^2.9.0", + "@bufbuild/protoc-gen-es": "^2.9.0", + "@connectrpc/connect-fastify": "^2.1.0", "@types/fs-extra": "*", "@types/mocha": "*", "@types/tmp": "*", "mocha": "*", - "next": "~14.0.0", + "next": "15.6.0-canary.54", + "protoc": "^32.1.0", "semver": "*", "tmp": "*", "ts-mocha": "*", "ts-node": "*", + "ts-proto": "^2.7.7", "typescript": "*", "verdaccio": "^5.30.3" } diff --git a/packages/@apphosting/adapter-nextjs/src/bin/build.spec.ts b/packages/@apphosting/adapter-nextjs/src/bin/build.spec.ts index e98d9f52..e68d5731 100644 --- a/packages/@apphosting/adapter-nextjs/src/bin/build.spec.ts +++ b/packages/@apphosting/adapter-nextjs/src/bin/build.spec.ts @@ -50,8 +50,6 @@ describe("build commands", () => { tmpDir, outputBundleOptions, path.join(tmpDir, ".next"), - defaultNextVersion, - adapterMetadata, ); await validateOutputDirectory(outputBundleOptions, path.join(tmpDir, ".next")); @@ -116,8 +114,6 @@ outputFiles: serverFilePath: path.join(tmpDir, ".next", "standalone", "apps", "next-app", "server.js"), }, path.join(tmpDir, ".next"), - defaultNextVersion, - adapterMetadata, ); const expectedFiles = { @@ -154,11 +150,6 @@ outputFiles: tmpDir, outputBundleOptions, path.join(tmpDir, ".next"), - defaultNextVersion, - { - adapterPackageName: "@apphosting/adapter-nextjs", - adapterVersion: "14.0.1", - }, ); assert.rejects( async () => await validateOutputDirectory(outputBundleOptions, path.join(tmpDir, ".next")), @@ -184,8 +175,6 @@ outputFiles: serverFilePath: path.join(standaloneAppPath, "server.js"), }, path.join(tmpDir, ".next"), - defaultNextVersion, - adapterMetadata, ); const expectedFiles = { @@ -214,8 +203,6 @@ outputFiles: tmpDir, outputBundleOptions, path.join(tmpDir, ".next"), - defaultNextVersion, - adapterMetadata, ); await validateOutputDirectory(outputBundleOptions, path.join(tmpDir, ".next")); @@ -240,11 +227,6 @@ outputFiles: tmpDir, outputBundleOptions, path.join(tmpDir, ".next"), - defaultNextVersion, - { - adapterPackageName: "@apphosting/adapter-nextjs", - adapterVersion: "14.0.1", - }, ); await validateOutputDirectory(outputBundleOptions, path.join(tmpDir, ".next")); @@ -276,8 +258,6 @@ outputFiles: tmpDir, outputBundleOptions, path.join(tmpDir, ".next"), - defaultNextVersion, - adapterMetadata, ); await validateOutputDirectory(outputBundleOptions, path.join(tmpDir, ".next")); diff --git a/packages/@apphosting/adapter-nextjs/src/bin/build.ts b/packages/@apphosting/adapter-nextjs/src/bin/build.ts index 1572adee..2e5744ad 100644 --- a/packages/@apphosting/adapter-nextjs/src/bin/build.ts +++ b/packages/@apphosting/adapter-nextjs/src/bin/build.ts @@ -1,78 +1,32 @@ #! /usr/bin/env node -import { - loadConfig, - populateOutputBundleOptions, - generateBuildOutput, - validateOutputDirectory, - getAdapterMetadata, - exists, -} from "../utils.js"; -import { join } from "path"; import { getBuildOptions, runBuild } from "@apphosting/common"; -import { - addRouteOverrides, - overrideNextConfig, - restoreNextConfig, - validateNextConfigOverride, -} from "../overrides.js"; +import { generateBuildOutput, loadConfig, populateOutputBundleOptions, validateOutputDirectory } from "../utils.js"; +import { join } from "node:path"; -const root = process.cwd(); -const opts = getBuildOptions(); - -// Set standalone mode -process.env.NEXT_PRIVATE_STANDALONE = "true"; // Opt-out sending telemetry to Vercel process.env.NEXT_TELEMETRY_DISABLED = "1"; -const nextConfig = await loadConfig(root, opts.projectDirectory); +process.env.NEXT_ADAPTER_PATH = join(import.meta.dirname, "..", "index.cjs"); + +await runBuild(); -/** - * Override user's Next Config to optimize the app for Firebase App Hosting - * and validate that the override resulted in a valid config that Next.js can - * load. - * - * We restore the user's Next Config at the end of the build, after the config file has been - * copied over to the output directory, so that the user's original code is not modified. - * - * If the app does not have a next.config.[js|mjs|ts] file in the first place, - * then can skip config override. - * - * Note: loadConfig always returns a fileName (default: next.config.js) even if - * one does not exist in the app's root: https://github.com/vercel/next.js/blob/23681508ca34b66a6ef55965c5eac57de20eb67f/packages/next/src/server/config.ts#L1115 - */ -const nextConfigPath = join(root, nextConfig.configFileName); -if (await exists(nextConfigPath)) { - await overrideNextConfig(root, nextConfig.configFileName); - await validateNextConfigOverride(root, opts.projectDirectory, nextConfig.configFileName); -} +const opts = getBuildOptions(); +const root = process.cwd(); -try { - await runBuild(); +const nextConfig = await loadConfig(root, opts.projectDirectory); - const adapterMetadata = getAdapterMetadata(); - const nextBuildDirectory = join(opts.projectDirectory, nextConfig.distDir); - const outputBundleOptions = populateOutputBundleOptions( +const nextBuildDirectory = join(opts.projectDirectory, nextConfig.distDir); +const outputBundleOptions = populateOutputBundleOptions( root, opts.projectDirectory, nextBuildDirectory, - ); +); - await addRouteOverrides( - outputBundleOptions.outputDirectoryAppPath, - nextConfig.distDir, - adapterMetadata, - ); - - const nextjsVersion = process.env.FRAMEWORK_VERSION || "unspecified"; - await generateBuildOutput( +await generateBuildOutput( root, opts.projectDirectory, outputBundleOptions, nextBuildDirectory, - nextjsVersion, - adapterMetadata, - ); - await validateOutputDirectory(outputBundleOptions, nextBuildDirectory); -} finally { - await restoreNextConfig(root, nextConfig.configFileName); -} +); + +await validateOutputDirectory(outputBundleOptions, nextBuildDirectory); diff --git a/packages/@apphosting/adapter-nextjs/src/bin/serve.ts b/packages/@apphosting/adapter-nextjs/src/bin/serve.ts new file mode 100644 index 00000000..177b0ca3 --- /dev/null +++ b/packages/@apphosting/adapter-nextjs/src/bin/serve.ts @@ -0,0 +1,571 @@ +#! /usr/bin/env node +import { readFileSync } from "node:fs"; +import { ServerResponse, IncomingMessage, createServer } from "node:http"; +import { join } from "node:path"; +import fastify from "fastify"; +import { AsyncLocalStorage } from "node:async_hooks"; +import { parse } from "node:url"; + +import { CommonResponse_ResponseStatus, ExternalProcessor } from "../protos/envoy/service/ext_proc/v3/external_processor_pb.js" +import { fastifyConnectPlugin } from "@connectrpc/connect-fastify"; + +import { HeaderValue } from "../protos/envoy/config/core/v3/base_pb.js"; +import { Socket } from "node:net"; +import { PassThrough } from "node:stream"; +import { ProcessingMode_BodySendMode, ProcessingMode_HeaderSendMode } from "../protos/envoy/extensions/filters/http/ext_proc/v3/processing_mode_pb.js"; + +// TODO bundle this rather than write/read from file +const buildContext = JSON.parse(readFileSync(join(process.cwd(), '.apphosting', 'output.json')).toString()); + +// The standalone directory is the root of the Next.js application. +const dir = join(process.cwd(), process.argv[2] || ".next/standalone"); +process.chdir(dir); + +// Standard NodeJS HTTP server port and hostname. +const port = parseInt(process.env.PORT!, 10) || 3000 +const hostname = process.env.HOSTNAME || '0.0.0.0' + +// Polyfill for the `self` global object, used by Next.js in minimal mode. +// @ts-ignore +globalThis.self = globalThis; +// Polyfill for the `AsyncLocalStorage` global object, used by Next.js in minimal mode. +globalThis.AsyncLocalStorage = AsyncLocalStorage; + +// Required by Next.js in minimal mode. +// @ts-ignore +process.env.NODE_ENV = "production"; +//process.env.MINIMAL_MODE = "1"; + +// Allow the keep-alive timeout to be configured. +let keepAliveTimeout: number | undefined = parseInt(process.env.KEEP_ALIVE_TIMEOUT!, 10); + +// Load the Next.js configuration from the standalone directory. +// TODO fix the types and jazz +const conf = { ...buildContext.config } as any; +conf.compress = false; + +// Pass the Next.js configuration to the Next.js server. +process.env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(conf); + +// Increase the max listeners to prevent warnings when many requests are in-flight. +process.setMaxListeners(1_000); + +// Dynamically import the Next.js middleware. +const resolveMiddleware = import(join(dir, ".next/server/middleware.js")); + +// TODO don't hardcode these matchers, they should be derived from the build output. +const middlewareMatchers: Array = buildContext.outputs.middleware.config.matchers.map((it: any) => new RegExp(it.sourceRegex)); + +const isPPRPath = (path: string) => { + const dynamicPathMatch = buildContext.routes.dynamicRoutes.find((it: any) => path.match(new RegExp(it.sourceRegex)))?.source; + const matchingPrerender = buildContext.outputs.prerenders.find((it: any) => it.pathname === dynamicPathMatch || it.pathname === path); + return matchingPrerender?.config?.renderingMode === "PARTIALLY_STATIC"; +} + +const getAppPage = (path: string) => { + const dynamicPathMatch = buildContext.routes.dynamicRoutes.find((it: any) => path.match(new RegExp(it.sourceRegex)))?.source; + return buildContext.outputs.appPages.find((it: any) => it.id === (dynamicPathMatch || path)); +} + +const getPrerender = (path: string) => { + const dynamicPathMatch = buildContext.routes.dynamicRoutes.find((it: any) => path.match(new RegExp(it.sourceRegex)))?.source; + return buildContext.outputs.prerenders.find((it: any) => it.id === (dynamicPathMatch || path)); +} + +const getMatchedDestination = (path: string) => { + for (const dynamicRoute of buildContext.routes.dynamicRoutes) { + const match = path.match(new RegExp(dynamicRoute.sourceRegex)); + if (match) { + return Object.entries(match.groups || {}).reduce((acc, [key, value]) => { return acc.replace(`\$${key}`, value) }, dynamicRoute.destination as string); + } + } +} + +// If the keep-alive timeout is not a valid number, use the default. +if ( + Number.isNaN(keepAliveTimeout) || + !Number.isFinite(keepAliveTimeout) || + keepAliveTimeout < 0 +) { + keepAliveTimeout = undefined +} + +// Initialize the Next.js server in minimal mode. +const resolveNextServer = import(join(dir, "node_modules/next/dist/server/next-server.js")).then(async ({ default: NextServer }) => { + const server = new NextServer.default({ + conf, + dev: false, + dir, + hostname, + port, + minimalMode: true, + }); + await server.prepare(); + return server; +}); + +/** + * Injects App Hosting specific headers into the response. + * + * This is used to communicate the postponed state of a page to the App Hosting backend. + * The backend will then use this information to resume the request when the page is + * ready. + * + * @param req The incoming request. + * @param res The server response. + */ +async function injectAppHostingHeaders(req: IncomingMessage, res: ServerResponse) { + if (req.method !== 'GET' && req.method !== 'HEAD') return; + if (!res.getHeaderNames().includes('x-nextjs-postponed')) return; + // TODO memoize/import this symbol outside the function + // TODO import the type from NextJS + const { NEXT_REQUEST_META } = await import(join(dir, "node_modules/next/dist/server/request-meta.js")); + const metadata = (req as any)[NEXT_REQUEST_META]; + // TODO use a stable API to get the resumption token + const cacheEntry = await metadata.incrementalCache.get(metadata.match.definition.pathname, { kind: metadata.match.definition.kind, isRoutePPREnabled: true }); + res.appendHeader('x-fah-postponed', Buffer.from(cacheEntry.value.postponed).toString('base64url')); +} + +/** + * Handles incoming HTTP requests. + * + * This function is the entry point for all HTTP requests. It is responsible for + * proxying requests to the Next.js server and for handling PPR (Partial Prerendering) + * requests. + * + * @param req The incoming request. + * @param res The server response. + */ +async function requestHandle(req: IncomingMessage, res: ServerResponse) { + const path = parse(req.url || '/', true).path || "/"; + const isPPR = ['GET', 'HEAD'].includes(req.method || 'GET') && isPPRPath(path); + const prerender = getPrerender(path); + const postponed = prerender?.fallback.postponedState; + // TODO this is a bug with the adapter spec, not seeing postponeData for "/" + if (isPPR && !postponed) { + console.error(`Unable to find postponed state for ${path} appending to response as x-fah-postponed`); + /** + * Next.js uses a request handler (`getRequestHandler`) which takes full + * `ServerResponse` object and doesn't provide any lifecycle hook. + * + * To inject our `x-fah-postponed` header *before* Next.js sends the + * first body chunk, we must monkey-patch `res.write` and `res.end`. + * We wrap them in a promise (`resolveHeaders`) to ensure our + * `injectAppHostingHeaders` function runs exactly once before any + * data is sent to the client. + */ + const originalWrite = res.write.bind(res); + let resolveHeaders: Promise | undefined; + // We need to append our headers before the body starts getting written + res.write = function () { + resolveHeaders ||= injectAppHostingHeaders(req, res); + resolveHeaders.then(() => { + // @ts-expect-error TODO fix the type + const written = originalWrite(...arguments); + if (written) res.emit("drain"); + }) + return false; + }; + const originalEnd = res.end.bind(res); + res.end = function () { + resolveHeaders ||= injectAppHostingHeaders(req, res); + resolveHeaders.then(() => { + originalEnd(...arguments); + }); + return res; + }; + } + const parsedUrl = parse(req.url!, true); + const nextServer = await resolveNextServer; + return nextServer.getRequestHandler()(req, res, parsedUrl); +}; + +/** + * The gRPC server that handles Envoy's external processing requests. + * + * This server is responsible for handling all gRPC requests from Envoy. It is + * used to implement middleware and to resume PPR requests. + */ +const grpcServer = fastify({ http2: true } as {}); + +await grpcServer.register(fastifyConnectPlugin, { + routes: (router) => router.service(ExternalProcessor, { + /** + * The `process` function is the entry point for all gRPC requests. + * + * It is a bidirectional streaming RPC that allows the data plane to send + * information about the HTTP request to the service and for the service to + * send back a `ProcessingResponse` message that directs the data plane on + * how to handle the request. + * + * https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/ext_proc/v3/external_processor.proto + * + * @param callouts The stream of `ProcessingRequest` messages from the data plane. + */ + process: async function *processCallouts(callouts) { + let requestHeaders: HeaderValue[] = []; + let resolveResumeBuffer: Promise|undefined; + let path: string|undefined = undefined; + let method: string|undefined = undefined; + let postponed: string|undefined = undefined; + // For whatever reason the header.value is always an empty string at least with + // my local version of envoy. I have to decode the rawValue every time + const getRequestHeader = (key: string) => { + const header = requestHeaders.find((it) => it.key === key); + if (header) return header.value || new TextDecoder().decode(header.rawValue); + return undefined; + } + for await (const callout of callouts) { + let modeOverride: any = {}; // TODO fix types + if (path) console.log(method, callout.request.case, path); + switch (callout.request.case) { + case "requestHeaders": { + requestHeaders = callout.request.value.headers?.headers || []; + // TODO look at the callout attributes, can we send thing like path + // so we don't have to parse from the pseudo headers + path = parse(getRequestHeader(":path") || "/", true).path!; + method = getRequestHeader(":method") || "GET"; + console.log(method, "requestHeaders", path); + const hasMiddleware = middlewareMatchers.some(it => path?.match(it)); + const usesPPR = ['GET', 'HEAD'].includes(method) && isPPRPath(path); + const hasRequestBody = !callout.request.value.endOfStream; + + // TODO only response on PPR + if (hasMiddleware && hasRequestBody) { + modeOverride.requestBodyMode = ProcessingMode_BodySendMode.BUFFERED; + } + + if (usesPPR) { + modeOverride.responseHeaderMode = ProcessingMode_HeaderSendMode.SEND; + modeOverride.responseBodyMode = ProcessingMode_BodySendMode.FULL_DUPLEX_STREAMED; + modeOverride.responseTrailerMode = ProcessingMode_HeaderSendMode.SEND; + }; + + if (!hasMiddleware) break; + + /** + * `requestHeaders` is the first callout we get. If `endOfStream` is + * true, it's a `GET` (or other body-less request), and we can run + * the middleware logic immediately. When there is no body + * `requestBody` would not otherwise be called. + * + * We do this by *intentionally falling through* to the `requestBody` + * case below, which contains our unified middleware logic. + * + * If `endOfStream` is false (e.g., a `POST`), we `break` and wait + * for the `requestBody` callout to arrive, which will then + * execute the *same* logic block. + */ + if (hasRequestBody) break; + } + case "requestBody": { + // TODO make a converter to a fetch request, make a helper to extract these headers + const scheme = getRequestHeader(":scheme")!; + const referrer = getRequestHeader("referer"); + const authority = getRequestHeader(":authority")!; + + // If the path does not match any of the middleware matchers, we can + // skip the middleware execution. + if (!middlewareMatchers.some(it => path?.match(it))) break; + + // Next.js middleware is intended for v8 isolates, with the fetch api. + // We construct a Fetch API compliant request object to pass to the + // middleware. + const middlewareRequest = { + url: `${scheme}://${authority}${path}`, + method, + // filter out http2 pseudo-headers, next chokes on these + headers: Object.fromEntries(requestHeaders.filter((it) => !it.key.startsWith(":")).map((it) => [it.key, it.value || new TextDecoder().decode(it.rawValue)]) || []), + // keepalive: req.keepalive, + destination: 'document', + credentials: 'same-origin', + bodyUsed: false, + // @ts-ignore + body: callout.request.case === "requestBody" ? callout.request.value.body : undefined, + mode: "navigate", + redirect: "follow", + referrer, + }; + const middleware = await resolveMiddleware; + let middlewareResponse: Response; + try { + const result = await middleware.default.default({ request: middlewareRequest }); + await result.waitUntil; + middlewareResponse = result.response; + } catch (err) { + console.error("Middleware execution failed:", err); + yield { + response: { + case: "immediateResponse", + value: { + status: { code: 500 }, + body: Uint8Array.from(Buffer.from("Internal Server Error")), + }, + }, + }; + continue; + } + // If the middleware returns a response with the `x-middleware-next` + // header, it means we should continue processing the request as if + // the middleware was not there. + if (middlewareResponse.headers.has("x-middleware-next")) break; + const middlewareResponseHeaders = Object.fromEntries(middlewareResponse.headers); + delete middlewareResponseHeaders["x-middleware-next"]; // Clean up middleware-specific header, TODO clean up other headers + + // Convert the Fetch Headers object to the { key, value } array Envoy expects + const setHeaders = Object.entries(middlewareResponseHeaders).map(([key, value]) => ({ + header: { key, rawValue: Uint8Array.from(Buffer.from(value)) }, + })); + + // If the middleware returns a response, we send it back to the client + // and stop processing the request. + yield { + response: { + case: "immediateResponse", + value: { + status: { code: middlewareResponse.status }, + headers: { + setHeaders + }, + body: Uint8Array.from(Buffer.from(await middlewareResponse.text())), + }, + } + } + continue; + } + case "responseHeaders": { + // This is where we handle PPR resumption. + // If the response has a `x-fah-postponed` header, it means the page + // is in a postponed state and we need to resume it. + const isPPR = ['GET', 'HEAD'].includes(method!) && isPPRPath(path!); + if (!isPPR) break; + + const postponedHeaderValue = callout.request.value.headers?.headers.find((it) => it.key === "x-fah-postponed")?.rawValue; + if (postponedHeaderValue) { + const tokenString = new TextDecoder().decode(postponedHeaderValue); + postponed = Buffer.from(tokenString, "base64url").toString(); + } + + // We tell Envoy to continue processing the request, but we also + // modify the headers to indicate that the response is chunked and + // to remove the `x-fah-postponed` and `content-length` headers. + yield { + response: { + case: "responseHeaders", + value: { + response: { + status: CommonResponse_ResponseStatus.CONTINUE, + headerMutation: { + setHeaders: [ + { header: { key: "transfer-encoding", rawValue: Uint8Array.from(Buffer.from("chunked")) } }, + ], + removeHeaders: ["content-length"], + }, + }, + }, + }, + } + + // We then kick off the resume request, so it's happening in parallel to the GET's + // body being sent to the client. Buffer it up. + resolveResumeBuffer = new Promise(async (resolve) => { + const socket = new Socket(); + const resumeRequest = new IncomingMessage(socket); + + const prerender = getPrerender(path!); + postponed ||= prerender?.fallback.postponedState; + + const appPage = getAppPage(path!); + if (!appPage) throw new Error('ahhhhhhhh!'); + + // We construct a new request to the Next.js server to resume the + // postponed page. + // This is the old way of doing PPR resumption, I'm having trouble with it in NextJS 16 + // TODO investigate a stable API or why this is bugging out on me + resumeRequest.url = path; + resumeRequest.method = method; + resumeRequest.httpVersion = "1.1"; + resumeRequest.httpVersionMajor = 1; + resumeRequest.httpVersionMinor = 1; + + const { NEXT_REQUEST_META } = await import(join(dir, "node_modules/next/dist/server/request-meta.js")); + (resumeRequest as any)[NEXT_REQUEST_META] = { postponed }; + + for (const header of requestHeaders) { + // drop HTTP2 pseudo headers + if (header.key.startsWith(":")) continue; + resumeRequest.headers[header.key] = getRequestHeader(header.key); + } + resumeRequest.headers['next-resume'] = "1"; + + const resumeResponse = new ServerResponse(resumeRequest); + const intermediaryStream = new PassThrough(); + + /** + * This is the core of the PPR streaming workaround. We cannot + * directly `await` the `resumeResponse` as it's a "push-style" + * classic Node.js stream, not a modern "pull-style" async iterable. + * + * To fix this, we create an `intermediaryStream` (a PassThrough) + * and manually override `resumeResponse.write` and `resumeResponse.end`. + * + * This effectively "pipes" the data from the Next.js handler (which + * *thinks* it's writing to a normal socket) into our intermediary + * stream, which we *can* await in the `responseBody` case. + * + * There's probably a "better" way of doing but the old school pipes + * in NodeJS are rough. It might be better to start with the new + * fetch style request/response and convert to InboundMessage / + * ServerResponse from those more modern APIs. + */ + resumeResponse.write = (data) => { + const result = intermediaryStream.push(data); + if (!result) intermediaryStream.on("drain", () => resumeResponse.emit("drain")); + return result; + }; + + resumeResponse.end = (data) => { + if (data) intermediaryStream.push(data); + intermediaryStream.end(); + return resumeResponse; + } + + const parsedUrl = parse(path!, true); + const appRouteFunction = await import(appPage.filePath); + appRouteFunction.default.handler(resumeRequest, resumeResponse, parsedUrl); + resolve(intermediaryStream); + }).catch((e) => { + console.error(e); + // TODO figure out how to tell react we crashed and need to client render + const intermediaryStream = new PassThrough(); + return intermediaryStream; + }); + + continue; + } + case "responseBody": { + // Let the original GET request be fulfilled, since we're using NextJS minimal-mode + // that request will be served in a CDN friendly manner, hopefully we have a hit ;) + + /** + * -------------------- Full-Duplex Mode ---------------------- + * + * Because we're using `streamedResponse` later (for PPR), we've + * configured Envoy for full-duplex streaming mode. + * + * In this mode, Envoy *always* expects us to send `streamedResponse` + * mutations. If we just `yield` a simple `CONTINUE` (our fallback) + * for a non-PPR request, Envoy's state machine gets confused + * and it will segfault. + * + * Therefore, for *all* requests, we must replace the response + * body with a stream, even if that stream is just the *original* + * response body. + * + * TODO: look into switching mode dynamically using `mode_override` + * in `responseHeaders` to avoid this for non-PPR requests. + * + * This logic determines the "passthrough" end_stream state. + * `end_stream` should *only* be true if: + * 1. We are *not* doing a PPR resume (`!resolveResumeBuffer`) + * 2. AND the original upstream chunk was the last one. + * + * TODO name resolveResumeBuffer better + */ + const end_stream = !resolveResumeBuffer && callout.request.value.endOfStream; + + // Serve up the original response, only EOF if this is not a PPR request and the + // original chunk was EOF. + const body = callout.request.value.body; + if (body.byteLength) { + yield { + response: { + case: "responseBody", + value: { + response: { + status: CommonResponse_ResponseStatus.CONTINUE_AND_REPLACE, + // Note: We use 'streamedResponse' even for the pass-through. + bodyMutation: { mutation: { case: 'streamedResponse', value: { body, endOfStream: end_stream } } }, + end_stream, + }, + }, + }, + }; + } + + // If the original response wasn't EOF yet, continue serving chunks (which will call this + // case again. + if (!callout.request.value.endOfStream) continue; + + const resumeBuffer = await resolveResumeBuffer!; + resolveResumeBuffer = undefined; // TODO do I need to do this? + + // Ok, let's start streaming in the PPR resume response + // full duplex mode is what allows us to yield multiple times, so we can stream, this + // is a marked improvement over the primitives available in proxy-Wasm at the moment. + for await (const body of resumeBuffer) { + yield { + response: { + case: "responseBody", + value: { + response: { + status: CommonResponse_ResponseStatus.CONTINUE_AND_REPLACE, + bodyMutation: { mutation: { case: 'streamedResponse', value: { body: Uint8Array.from(body), endOfStream: false } } }, + end_stream: false, + }, + }, + }, + }; + } + + // Finally send EOF + yield { + response: { + case: "responseBody", + value: { + response: { + status: CommonResponse_ResponseStatus.CONTINUE_AND_REPLACE, + bodyMutation: { mutation: { case: 'streamedResponse', value: { body: Buffer.alloc(0), endOfStream: true } } }, + end_stream: true, + }, + }, + }, + }; + continue; + } + // TODO can we intercept trailers to handle waitFor functionality? + } + // If we fall through the switch, it means we are not handling the + // request in any special way, so we just tell Envoy to continue. + const empty = {}; + yield { + response: { + case: callout.request.case, + value: { + response: { + status: CommonResponse_ResponseStatus.CONTINUE, + } + }, + }, + modeOverride: modeOverride!, + } as (typeof empty); + } + } + }), +}); + +// Create the main HTTP server. +createServer(requestHandle).listen(port, hostname, () => { + console.log(`NextJS listening on http://${hostname}:${port}`); +}).on("error", (err) => { + console.error(err); + process.exit(1); +}); + +await grpcServer.ready(); + +// Start the gRPC server. +grpcServer.listen({ host: hostname, port: port+1 }, (err, address) => { + if (err) return console.error(err); + console.log(`RPC listening on ${address}`); +}); \ No newline at end of file diff --git a/packages/@apphosting/adapter-nextjs/src/index.ts b/packages/@apphosting/adapter-nextjs/src/index.ts index cb0ff5c3..e488f57d 100644 --- a/packages/@apphosting/adapter-nextjs/src/index.ts +++ b/packages/@apphosting/adapter-nextjs/src/index.ts @@ -1 +1,76 @@ -export {}; +import { generateBundleYaml, getAdapterMetadata, populateOutputBundleOptions } from "./utils.js"; +import type { NextAdapter } from "next"; +import { addRouteOverrides } from "./overrides.js"; +import { PHASE_PRODUCTION_BUILD } from "./constants.js"; +import { writeFile } from "fs-extra"; + +const adapter: NextAdapter = { + name: '@apphosting/adapter-nextjs', + // FEEDBACK: we need to be able to override user-defined config, before defaults injected + // it would be nice if this where a separate phase or callback + async modifyConfig(config, context) { + console.log(JSON.stringify(context)); + if (context.phase === PHASE_PRODUCTION_BUILD) { + return { + ...config, + images: { + ...(config.images || {}), + ...(config.images?.unoptimized === undefined && config.images?.loader === undefined + ? { unoptimized: true } + : {}), + }, + headers: async () => { + const originalHeaders = config.headers && await config.headers() || []; + const adapterMetadata = getAdapterMetadata(); + // TODO add our middleware header OR not... :P + return [ + ...originalHeaders, + { + source: "/(.*)", + headers: [{ + key: "x-fah-adapter", + value: `nextjs-${adapterMetadata.adapterVersion}`, + }], + }, + ] + }, + experimental: { + ...(config.experimental || {}), + nodeMiddleware: true, + }, + output: 'standalone', + } + } + // TODO override config for production build + return config; + }, + // This fires before standalone is bundled, so we need to pick things back up after build in bin/build.ts + // FEEDBACK: can we get a hook after bundle? + async onBuildComplete(context) { + + const nextBuildDirectory = context.distDir; + + if (context.outputs.middleware?.config?.matchers) { + await addRouteOverrides(nextBuildDirectory, context.outputs.middleware.config.matchers); + } + + const outputBundleOptions = populateOutputBundleOptions( + context.repoRoot, + context.projectDir, + nextBuildDirectory, + ); + + await writeFile(`${outputBundleOptions.outputDirectoryBasePath}/output.json`, JSON.stringify(context)); + + const adapterMetadata = getAdapterMetadata(); + + const root = process.cwd(); + + const nextjsVersion = process.env.FRAMEWORK_VERSION || context.nextVersion || "unspecified"; + + await generateBundleYaml(outputBundleOptions, root, nextjsVersion, adapterMetadata); + + }, +}; + +module.exports = adapter; diff --git a/packages/@apphosting/adapter-nextjs/src/overrides.spec.ts b/packages/@apphosting/adapter-nextjs/src/overrides.spec.ts deleted file mode 100644 index e223037c..00000000 --- a/packages/@apphosting/adapter-nextjs/src/overrides.spec.ts +++ /dev/null @@ -1,445 +0,0 @@ -import assert from "assert"; -import fs from "fs"; -import path from "path"; -import os from "os"; -import { RoutesManifest, MiddlewareManifest } from "./interfaces.js"; -const importOverrides = import("@apphosting/adapter-nextjs/dist/overrides.js"); - -describe("route overrides", () => { - let tmpDir: string; - let routesManifestPath: string; - let middlewareManifestPath: string; - - beforeEach(() => { - tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "test-manifests-")); - routesManifestPath = path.join(tmpDir, ".next", "routes-manifest.json"); - middlewareManifestPath = path.join(tmpDir, ".next", "server", "middleware-manifest.json"); - - fs.mkdirSync(path.dirname(routesManifestPath), { recursive: true }); - fs.mkdirSync(path.dirname(middlewareManifestPath), { recursive: true }); - }); - - it("should add default fah headers to routes manifest", async () => { - const { addRouteOverrides } = await importOverrides; - const initialManifest: RoutesManifest = { - version: 3, - basePath: "", - pages404: true, - staticRoutes: [], - dynamicRoutes: [], - dataRoutes: [], - headers: [ - { - source: "/existing", - headers: [{ key: "X-Custom", value: "test" }], - regex: "^/existing$", - }, - ], - rewrites: [], - redirects: [], - }; - - fs.writeFileSync(routesManifestPath, JSON.stringify(initialManifest)); - fs.writeFileSync( - middlewareManifestPath, - JSON.stringify({ version: 1, sortedMiddleware: [], middleware: {}, functions: {} }), - ); - - await addRouteOverrides(tmpDir, ".next", { - adapterPackageName: "@apphosting/adapter-nextjs", - adapterVersion: "1.0.0", - }); - - const updatedManifest = JSON.parse( - fs.readFileSync(routesManifestPath, "utf-8"), - ) as RoutesManifest; - - const expectedManifest: RoutesManifest = { - version: 3, - basePath: "", - pages404: true, - staticRoutes: [], - dynamicRoutes: [], - dataRoutes: [], - redirects: [], - rewrites: [], - headers: [ - { - source: "/existing", - headers: [{ key: "X-Custom", value: "test" }], - regex: "^/existing$", - }, - { - source: "/:path*", - regex: "^(?:/((?:[^/]+?)(?:/(?:[^/]+?))*))?(?:/)?$", - headers: [ - { - key: "x-fah-adapter", - value: "nextjs-1.0.0", - }, - ], - }, - ], - }; - - assert.deepStrictEqual(updatedManifest, expectedManifest); - }); - - it("should add middleware header only to routes for which middleware is enabled", async () => { - const { addRouteOverrides } = await importOverrides; - const initialManifest: RoutesManifest = { - version: 3, - basePath: "", - pages404: true, - staticRoutes: [], - dynamicRoutes: [], - dataRoutes: [], - headers: [], - rewrites: [], - redirects: [], - }; - - const middlewareManifest: MiddlewareManifest = { - version: 3, - sortedMiddleware: ["/"], - middleware: { - "/": { - files: ["middleware.ts"], - name: "middleware", - page: "/", - matchers: [ - { - regexp: "/hello", - originalSource: "/hello", - }, - ], - }, - }, - functions: {}, - }; - - fs.writeFileSync(routesManifestPath, JSON.stringify(initialManifest)); - fs.writeFileSync(middlewareManifestPath, JSON.stringify(middlewareManifest)); - - await addRouteOverrides(tmpDir, ".next", { - adapterPackageName: "@apphosting/adapter-nextjs", - adapterVersion: "1.0.0", - }); - - const updatedManifest = JSON.parse( - fs.readFileSync(routesManifestPath, "utf-8"), - ) as RoutesManifest; - - assert.strictEqual(updatedManifest.headers.length, 2); - - const expectedManifest: RoutesManifest = { - version: 3, - basePath: "", - pages404: true, - staticRoutes: [], - dynamicRoutes: [], - dataRoutes: [], - rewrites: [], - redirects: [], - headers: [ - { - source: "/:path*", - regex: "^(?:/((?:[^/]+?)(?:/(?:[^/]+?))*))?(?:/)?$", - headers: [ - { - key: "x-fah-adapter", - value: "nextjs-1.0.0", - }, - ], - }, - { - source: "/hello", - regex: "/hello", - headers: [{ key: "x-fah-middleware", value: "true" }], - }, - ], - }; - - assert.deepStrictEqual(updatedManifest, expectedManifest); - }); - - afterEach(() => { - fs.rmSync(tmpDir, { recursive: true, force: true }); - }); -}); - -describe("next config overrides", () => { - let tmpDir: string; - const nextConfigOverrideBody = ` - // This file was automatically generated by Firebase App Hosting adapter - const fahOptimizedConfig = (config) => ({ - ...config, - images: { - ...(config.images || {}), - ...(config.images?.unoptimized === undefined && config.images?.loader === undefined - ? { unoptimized: true } - : {}), - }, - }); - - const config = typeof originalConfig === 'function' - ? async (...args) => { - const resolvedConfig = await originalConfig(...args); - return fahOptimizedConfig(resolvedConfig); - } - : fahOptimizedConfig(originalConfig); - `; - - beforeEach(() => { - tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "test-overrides")); - }); - - it("should set images.unoptimized to true - js normal config", async () => { - const { overrideNextConfig } = await importOverrides; - const originalConfig = ` - // @ts-check - - /** @type {import('next').NextConfig} */ - const nextConfig = { - /* config options here */ - } - - module.exports = nextConfig - `; - - fs.writeFileSync(path.join(tmpDir, "next.config.js"), originalConfig); - await overrideNextConfig(tmpDir, "next.config.js"); - - const updatedConfig = fs.readFileSync(path.join(tmpDir, "next.config.js"), "utf-8"); - - assert.equal( - normalizeWhitespace(updatedConfig), - normalizeWhitespace(` - // @ts-nocheck - const originalConfig = require('./next.config.original.js'); - - ${nextConfigOverrideBody} - - module.exports = config; - `), - ); - }); - - it("should set images.unoptimized to true - ECMAScript Modules", async () => { - const { overrideNextConfig } = await importOverrides; - const originalConfig = ` - // @ts-check - - /** - * @type {import('next').NextConfig} - */ - const nextConfig = { - /* config options here */ - } - - export default nextConfig - `; - - fs.writeFileSync(path.join(tmpDir, "next.config.mjs"), originalConfig); - await overrideNextConfig(tmpDir, "next.config.mjs"); - - const updatedConfig = fs.readFileSync(path.join(tmpDir, "next.config.mjs"), "utf-8"); - assert.equal( - normalizeWhitespace(updatedConfig), - normalizeWhitespace(` - // @ts-nocheck - import originalConfig from './next.config.original.mjs'; - - ${nextConfigOverrideBody} - - export default config; - `), - ); - }); - - it("should set images.unoptimized to true - ECMAScript Function", async () => { - const { overrideNextConfig } = await importOverrides; - const originalConfig = ` - // @ts-check - - export default (phase, { defaultConfig }) => { - /** - * @type {import('next').NextConfig} - */ - const nextConfig = { - /* config options here */ - } - return nextConfig - } - `; - - fs.writeFileSync(path.join(tmpDir, "next.config.mjs"), originalConfig); - await overrideNextConfig(tmpDir, "next.config.mjs"); - - const updatedConfig = fs.readFileSync(path.join(tmpDir, "next.config.mjs"), "utf-8"); - assert.equal( - normalizeWhitespace(updatedConfig), - normalizeWhitespace(` - // @ts-nocheck - import originalConfig from './next.config.original.mjs'; - - ${nextConfigOverrideBody} - - export default config; - `), - ); - }); - - it("should set images.unoptimized to true - TypeScript", async () => { - const { overrideNextConfig } = await importOverrides; - const originalConfig = ` - import type { NextConfig } from 'next' - - const nextConfig: NextConfig = { - /* config options here */ - } - - export default nextConfig - `; - - fs.writeFileSync(path.join(tmpDir, "next.config.ts"), originalConfig); - await overrideNextConfig(tmpDir, "next.config.ts"); - - const updatedConfig = fs.readFileSync(path.join(tmpDir, "next.config.ts"), "utf-8"); - assert.equal( - normalizeWhitespace(updatedConfig), - normalizeWhitespace(` - // @ts-nocheck - import originalConfig from './next.config.original'; - - ${nextConfigOverrideBody} - - module.exports = config; - `), - ); - }); - - it("should not do anything if no next.config.* file exists", async () => { - const { overrideNextConfig } = await importOverrides; - await overrideNextConfig(tmpDir, "next.config.js"); - - // Assert that no next.config* files were created - const files = fs.readdirSync(tmpDir); - const nextConfigFiles = files.filter((file) => file.startsWith("next.config")); - assert.strictEqual(nextConfigFiles.length, 0, "No next.config files should exist"); - }); -}); - -describe("validateNextConfigOverride", () => { - let tmpDir: string; - let root: string; - let projectRoot: string; - let originalConfigFileName: string; - let newConfigFileName: string; - let originalConfigPath: string; - let newConfigPath: string; - - beforeEach(() => { - tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "test-next-config-override")); - root = tmpDir; - projectRoot = tmpDir; - originalConfigFileName = "next.config.js"; - newConfigFileName = "next.config.original.js"; - originalConfigPath = path.join(root, originalConfigFileName); - newConfigPath = path.join(root, newConfigFileName); - - fs.mkdirSync(root, { recursive: true }); - }); - - afterEach(() => { - fs.rmSync(tmpDir, { recursive: true, force: true }); - }); - - it("should throw an error when new config file doesn't exist", async () => { - fs.writeFileSync(originalConfigPath, "module.exports = {}"); - - const { validateNextConfigOverride } = await importOverrides; - - await assert.rejects( - async () => await validateNextConfigOverride(root, projectRoot, originalConfigFileName), - /New Next.js config file not found/, - ); - }); - - it("should throw an error when original config file doesn't exist", async () => { - fs.writeFileSync(newConfigPath, "module.exports = {}"); - - const { validateNextConfigOverride } = await importOverrides; - - await assert.rejects( - async () => await validateNextConfigOverride(root, projectRoot, originalConfigFileName), - /Original Next.js config file not found/, - ); - }); -}); - -describe("next config restore", () => { - let tmpDir: string; - const nextConfigOriginalBody = ` - // @ts-check - - /** @type {import('next').NextConfig} */ - const nextConfig = { - /* config options here */ - } - - module.exports = nextConfig - `; - const nextConfigBody = ` - // This file was automatically generated by Firebase App Hosting adapter - const fahOptimizedConfig = (config) => ({ - ...config, - images: { - ...(config.images || {}), - ...(config.images?.unoptimized === undefined && config.images?.loader === undefined - ? { unoptimized: true } - : {}), - }, - }); - - const config = typeof originalConfig === 'function' - ? async (...args) => { - const resolvedConfig = await originalConfig(...args); - return fahOptimizedConfig(resolvedConfig); - } - : fahOptimizedConfig(originalConfig); - `; - - beforeEach(() => { - tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "test-overrides")); - }); - - it("handle no original config file found", async () => { - const { restoreNextConfig } = await importOverrides; - fs.writeFileSync(path.join(tmpDir, "next.config.mjs"), nextConfigBody); - await restoreNextConfig(tmpDir, "next.config.mjs"); - - const restoredConfig = fs.readFileSync(path.join(tmpDir, "next.config.mjs"), "utf-8"); - assert.equal(restoredConfig, nextConfigBody); - }); - - it("handle no config file found", async () => { - const { restoreNextConfig } = await importOverrides; - assert.doesNotReject(restoreNextConfig(tmpDir, "next.config.mjs")); - }); - - it("original config file restored", async () => { - const { restoreNextConfig } = await importOverrides; - fs.writeFileSync(path.join(tmpDir, "next.config.mjs"), nextConfigBody); - fs.writeFileSync(path.join(tmpDir, "next.config.original.mjs"), nextConfigOriginalBody); - await restoreNextConfig(tmpDir, "next.config.mjs"); - - const restoredConfig = fs.readFileSync(path.join(tmpDir, "next.config.mjs"), "utf-8"); - assert.equal(restoredConfig, nextConfigOriginalBody); - }); -}); - -// Normalize whitespace for comparison -function normalizeWhitespace(str: string) { - return str.replace(/\s+/g, " ").trim(); -} diff --git a/packages/@apphosting/adapter-nextjs/src/overrides.ts b/packages/@apphosting/adapter-nextjs/src/overrides.ts index 5edd0d54..a2d7f656 100644 --- a/packages/@apphosting/adapter-nextjs/src/overrides.ts +++ b/packages/@apphosting/adapter-nextjs/src/overrides.ts @@ -1,169 +1,8 @@ -import { AdapterMetadata } from "./interfaces.js"; +import type { MiddlewareMatcher } from "next/dist/build/analysis/get-page-static-info.js"; import { loadRouteManifest, writeRouteManifest, - loadMiddlewareManifest, - exists, - writeFile, - loadConfig, } from "./utils.js"; -import { join, extname } from "path"; -import { rename as renamePromise } from "fs/promises"; - -/** - * Overrides the user's Next Config file (next.config.[ts|js|mjs]) to add configs - * optimized for Firebase App Hosting. - */ -export async function overrideNextConfig(projectRoot: string, nextConfigFileName: string) { - console.log(`Overriding Next Config to add configs optmized for Firebase App Hosting`); - // Check if the file exists in the current working directory - const configPath = join(projectRoot, nextConfigFileName); - - if (!(await exists(configPath))) { - console.log(`No Next.js config file found at ${configPath}`); - return; - } - - // Determine the file extension - const fileExtension = extname(nextConfigFileName); - const originalConfigName = `next.config.original${fileExtension}`; - - // Rename the original config file - try { - const originalPath = join(projectRoot, originalConfigName); - await renamePromise(configPath, originalPath); - - // Create a new config file with the appropriate import - let importStatement; - switch (fileExtension) { - case ".js": - importStatement = `const originalConfig = require('./${originalConfigName}');`; - break; - case ".mjs": - importStatement = `import originalConfig from './${originalConfigName}';`; - break; - case ".ts": - importStatement = `import originalConfig from './${originalConfigName.replace( - ".ts", - "", - )}';`; - break; - default: - throw new Error( - `Unsupported file extension for Next Config: "${fileExtension}", please use ".js", ".mjs", or ".ts"`, - ); - } - - // Create the new config content with our overrides - const newConfigContent = getCustomNextConfig(importStatement, fileExtension); - - // Write the new config file - await writeFile(join(projectRoot, nextConfigFileName), newConfigContent); - console.log(`Successfully created ${nextConfigFileName} with Firebase App Hosting overrides`); - } catch (error) { - console.error(`Error overriding Next.js config: ${error}`); - throw error; - } -} - -/** - * Returns a custom Next.js config that optimizes the app for Firebase App Hosting. - * - * Current overrides include: - * - images.unoptimized = true, unless user explicitly sets images.unoptimized to false or - * is using a custom image loader. - * @param importStatement The import statement for the original config. - * @param fileExtension The file extension of the original config. Use ".js", ".mjs", or ".ts" - * @return The custom Next.js config. - */ -function getCustomNextConfig(importStatement: string, fileExtension: string) { - return ` - // @ts-nocheck - ${importStatement} - - // This file was automatically generated by Firebase App Hosting adapter - const fahOptimizedConfig = (config) => ({ - ...config, - images: { - ...(config.images || {}), - ...(config.images?.unoptimized === undefined && config.images?.loader === undefined - ? { unoptimized: true } - : {}), - }, - }); - - const config = typeof originalConfig === 'function' - ? async (...args) => { - const resolvedConfig = await originalConfig(...args); - return fahOptimizedConfig(resolvedConfig); - } - : fahOptimizedConfig(originalConfig); - - ${fileExtension === ".mjs" ? "export default config;" : "module.exports = config;"} - `; -} - -/** - * This function is used to validate the state of an app after running the - * overrideNextConfig. It validates that: - * 1. original next config is preserved - * 2. a new next config is created - * 3. new next config can be loaded by NextJs without any issues. - */ -export async function validateNextConfigOverride( - root: string, - projectRoot: string, - originalConfigFileName: string, -) { - const originalConfigExtension = extname(originalConfigFileName); - const newConfigFileName = `next.config.original${originalConfigExtension}`; - const newConfigFilePath = join(root, newConfigFileName); - if (!(await exists(newConfigFilePath))) { - throw new Error( - `Next Config Override Failed: New Next.js config file not found at ${newConfigFilePath}`, - ); - } - - const originalNextConfigFilePath = join(root, originalConfigFileName); - if (!(await exists(originalNextConfigFilePath))) { - throw new Error( - `Next Config Override Failed: Original Next.js config file not found at ${originalNextConfigFilePath}`, - ); - } - - try { - await loadConfig(root, projectRoot); - } catch (error) { - throw new Error( - `Resulting Next Config is invalid: ${ - error instanceof Error ? error.message : "Unknown error" - }`, - ); - } -} - -/** - * Restores the user's original Next Config file (next.config.original.[ts|js|mjs]) - * to leave user code the way we found it. - */ -export async function restoreNextConfig(projectRoot: string, nextConfigFileName: string) { - // Determine the file extension - const fileExtension = extname(nextConfigFileName); - const originalConfigPath = join(projectRoot, `next.config.original${fileExtension}`); - - if (!(await exists(originalConfigPath))) { - // No backup file found, nothing to restore. - return; - } - console.log(`Restoring original next config in project root`); - - const configPath = join(projectRoot, nextConfigFileName); - try { - await renamePromise(originalConfigPath, configPath); - } catch (error) { - console.error(`Error restoring Next config: ${error}`); - } -} /** * Modifies the app's route manifest (routes-manifest.json) to add Firebase App Hosting @@ -179,39 +18,12 @@ export async function restoreNextConfig(projectRoot: string, nextConfigFileName: * @param adapterMetadata The adapter metadata. */ export async function addRouteOverrides( - appPath: string, distDir: string, - adapterMetadata: AdapterMetadata, + middlewareMatchers: MiddlewareMatcher[], ) { - const routeManifest = loadRouteManifest(appPath, distDir); - - // Add the adapter version to all routes - routeManifest.headers.push({ - source: "/:path*", - headers: [ - { - key: "x-fah-adapter", - value: `nextjs-${adapterMetadata.adapterVersion}`, - }, - ], - /* - NextJs converts the source string to a regex using path-to-regexp (https://github.com/pillarjs/path-to-regexp) at - build time: https://github.com/vercel/next.js/blob/canary/packages/next/src/build/index.ts#L1273. - This regex is then used to match the route against the request path. - - This regex was generated by building a sample NextJs app with the source string `/:path*` and then inspecting the - routes-manifest.json file. - */ - regex: "^(?:/((?:[^/]+?)(?:/(?:[^/]+?))*))?(?:/)?$", - }); - - // Add the middleware header to all routes for which middleware is enabled - const middlewareManifest = loadMiddlewareManifest(appPath, distDir); - const rootMiddleware = middlewareManifest.middleware["/"]; - if (rootMiddleware?.matchers) { - console.log("Middleware detected, adding middleware headers to matching routes"); + const routeManifest = loadRouteManifest(distDir); - rootMiddleware.matchers.forEach((matcher) => { + middlewareMatchers.forEach((matcher) => { routeManifest.headers.push({ source: matcher.originalSource, headers: [ @@ -223,7 +35,6 @@ export async function addRouteOverrides( regex: matcher.regexp, }); }); - } - await writeRouteManifest(appPath, distDir, routeManifest); -} + await writeRouteManifest(distDir, routeManifest); +} \ No newline at end of file diff --git a/packages/@apphosting/adapter-nextjs/src/utils.spec.ts b/packages/@apphosting/adapter-nextjs/src/utils.spec.ts index 5ae25a08..c55f4dc5 100644 --- a/packages/@apphosting/adapter-nextjs/src/utils.spec.ts +++ b/packages/@apphosting/adapter-nextjs/src/utils.spec.ts @@ -4,7 +4,7 @@ import assert from "assert"; import fs from "fs"; import path from "path"; import os from "os"; -import { RoutesManifest, MiddlewareManifest } from "../src/interfaces.js"; +import { RoutesManifest, MiddlewareManifest } from "./interfaces.js"; describe("manifest utils", () => { let tmpDir: string; diff --git a/packages/@apphosting/adapter-nextjs/src/utils.ts b/packages/@apphosting/adapter-nextjs/src/utils.ts index 3c6ab548..fc4ea9f9 100644 --- a/packages/@apphosting/adapter-nextjs/src/utils.ts +++ b/packages/@apphosting/adapter-nextjs/src/utils.ts @@ -1,23 +1,30 @@ import fsExtra from "fs-extra"; -import { createRequire } from "node:module"; import { join, dirname, relative, normalize } from "path"; -import { fileURLToPath } from "url"; import { stringify as yamlStringify } from "yaml"; +import { createRequire } from "node:module"; -import { PHASE_PRODUCTION_BUILD, ROUTES_MANIFEST, MIDDLEWARE_MANIFEST } from "./constants.js"; import { OutputBundleOptions, - RoutesManifest, AdapterMetadata, - MiddlewareManifest, + RoutesManifest, } from "./interfaces.js"; -import { NextConfigComplete } from "next/dist/server/config-shared.js"; import { OutputBundleConfig, updateOrCreateGitignore } from "@apphosting/common"; +import { fileURLToPath } from "url"; + +import { PHASE_PRODUCTION_BUILD, ROUTES_MANIFEST } from "./constants.js"; +import type { NextConfigComplete } from "next/dist/server/config-shared.js"; +import { spawnSync } from "node:child_process"; // fs-extra is CJS, readJson can't be imported using shorthand export const { copy, exists, writeFile, readJson, readdir, readFileSync, existsSync, ensureDir } = fsExtra; +export const isMain = (meta: ImportMeta): boolean => { + if (!meta) return false; + if (!process.argv[1]) return false; + return process.argv[1] === fileURLToPath(meta.url); +}; + // Loads the user's next.config.js file. export async function loadConfig(root: string, projectRoot: string): Promise { // createRequire() gives us access to Node's CommonJS implementation of require.resolve() @@ -38,53 +45,6 @@ export async function loadConfig(root: string, projectRoot: string): Promise { - const manifestPath = join(standalonePath, distDir, ROUTES_MANIFEST); - await writeFile(manifestPath, JSON.stringify(customManifest)); -} - -export const isMain = (meta: ImportMeta): boolean => { - if (!meta) return false; - if (!process.argv[1]) return false; - return process.argv[1] === fileURLToPath(meta.url); -}; /** * Provides the paths in the output bundle for the built artifacts. @@ -116,7 +76,34 @@ export function populateOutputBundleOptions( } /** - * Copy static assets and other resources into the standlone directory, also generates the bundle.yaml + * Loads the route manifest from the standalone directory. + * @param standalonePath The path to the standalone directory. + * @param distDir The path to the dist directory. + * @return The route manifest. + */ +export function loadRouteManifest(distDir: string): RoutesManifest { + const manifestPath = join(distDir, ROUTES_MANIFEST); + const json = readFileSync(manifestPath, "utf-8"); + return JSON.parse(json) as RoutesManifest; +} + + +/** + * Writes the route manifest to the standalone directory. + * @param standalonePath The path to the standalone directory. + * @param distDir The path to the dist directory. + * @param customManifest The route manifest to write. + */ +export async function writeRouteManifest( + distDir: string, + customManifest: RoutesManifest, +): Promise { + const manifestPath = join(distDir, ROUTES_MANIFEST); + await writeFile(manifestPath, JSON.stringify(customManifest)); +} + +/** + * Copy static assets and other resources into the standalone directory, also generates the bundle.yaml * @param rootDir The root directory of the uploaded source code. * @param outputBundleOptions The target location of built artifacts in the output bundle. * @param nextBuildDirectory The location of the .next directory. @@ -126,19 +113,23 @@ export async function generateBuildOutput( appDir: string, opts: OutputBundleOptions, nextBuildDirectory: string, - nextVersion: string, - adapterMetadata: AdapterMetadata, ): Promise { - const staticDirectory = join(nextBuildDirectory, "static"); - await Promise.all([ - copy(staticDirectory, opts.outputStaticDirectoryPath, { overwrite: true }), - copyResources(appDir, opts.outputDirectoryAppPath, opts.bundleYamlPath), - generateBundleYaml(opts, rootDir, nextVersion, adapterMetadata), - ]); + const minimalMode = !!process.env.FAH_MINIMAL_MODE; + if (!minimalMode) { + const staticDirectory = join(nextBuildDirectory, "static"); + const publicDirectory = join(appDir, "public"); + await Promise.all([ + copy(staticDirectory, opts.outputStaticDirectoryPath, { overwrite: true }), + copy(publicDirectory, opts.outputPublicDirectoryPath, { overwrite: true }).catch(() => undefined), + //copyResources(appDir, opts.outputDirectoryAppPath, opts.bundleYamlPath), + ]); + } // generateBundleYaml creates the output directory (if it does not already exist). // We need to make sure it is gitignored. const normalizedBundleDir = normalize(relative(rootDir, opts.outputDirectoryBasePath)); updateOrCreateGitignore(rootDir, [`/${normalizedBundleDir}/`]); + await copy(join(appDir, "node_modules/@apphosting/adapter-nextjs"), join(opts.outputDirectoryAppPath, "adapter"), { overwrite: true }); + spawnSync("npm", ["i", "--omit=dev"], { shell: true, cwd: join(opts.outputDirectoryAppPath, "adapter")}); return; } @@ -152,7 +143,8 @@ async function copyResources( const appDirExists = await exists(appDir); if (!appDirExists) return; const pathsToCopy = await readdir(appDir); - for (const path of pathsToCopy) { + console.log(pathsToCopy); + await Promise.all(pathsToCopy.map(async (path) => { const isbundleYamlDir = join(appDir, path) === dirname(bundleYamlPath); const existsInOutputBundle = await exists(join(outputBundleAppDir, path)); // Keep apphosting.yaml files in the root directory still, as later steps expect them to be there @@ -160,13 +152,12 @@ async function copyResources( if (!isbundleYamlDir && !existsInOutputBundle && !isApphostingYaml) { await copy(join(appDir, path), join(outputBundleAppDir, path)); } - } + })); return; } export function getAdapterMetadata(): AdapterMetadata { - const directoryName = dirname(fileURLToPath(import.meta.url)); - const packageJsonPath = `${directoryName}/../package.json`; + const packageJsonPath = `${__dirname}/../package.json`; if (!existsSync(packageJsonPath)) { throw new Error(`Next.js adapter package.json file does not exist at ${packageJsonPath}`); } @@ -179,17 +170,18 @@ export function getAdapterMetadata(): AdapterMetadata { } // generate bundle.yaml -async function generateBundleYaml( +export async function generateBundleYaml( opts: OutputBundleOptions, cwd: string, nextVersion: string, adapterMetadata: AdapterMetadata, ): Promise { await ensureDir(opts.outputDirectoryBasePath); + const path = normalize(relative(cwd, join(opts.outputDirectoryAppPath))); const outputBundle: OutputBundleConfig = { version: "v1", runConfig: { - runCommand: `node ${normalize(relative(cwd, opts.serverFilePath))}`, + runCommand: `node ${join(path, "adapter", "dist", "bin", "serve.js")} ${path}`, }, metadata: { ...adapterMetadata, diff --git a/packages/@apphosting/adapter-nextjs/tsconfig.json b/packages/@apphosting/adapter-nextjs/tsconfig.json index 0b56b766..607d9ffd 100644 --- a/packages/@apphosting/adapter-nextjs/tsconfig.json +++ b/packages/@apphosting/adapter-nextjs/tsconfig.json @@ -8,6 +8,7 @@ "include": [ "src/index.ts", "src/bin/*.ts", + "src/bin/*.cts" ], "exclude": [ "src/*.spec.ts" diff --git a/starters/nextjs/shopify-ecommerce/dataconnect/.dataconnect/schema/main/input.gql b/starters/nextjs/shopify-ecommerce/dataconnect/.dataconnect/schema/main/input.gql index 037e0876..0396f16c 100644 --- a/starters/nextjs/shopify-ecommerce/dataconnect/.dataconnect/schema/main/input.gql +++ b/starters/nextjs/shopify-ecommerce/dataconnect/.dataconnect/schema/main/input.gql @@ -59,6 +59,14 @@ input Product_Data { """ price: Float """ + ✨ `_expr` server value variant of `price` (✨ Generated from Field `Product`.`price` of type `Float!`) + """ + price_expr: Float_Expr + """ + ✨ `_update` server value variant of `price` (✨ Generated from Field `Product`.`price` of type `Float!`) + """ + price_update: [Float_Update!] + """ ✨ Generated from Field `Product`.`productID` of type `String!` """ productID: String @@ -138,6 +146,79 @@ input Product_FirstRow { where: Product_Filter } """ +✨ Generated having input type for table 'Product'. This input allows you to filter groups during aggregate queries using various conditions. Use `_or`, `_and`, and `_not` to compose complex filters. +""" +input Product_Having { + """ + Apply multiple Having conditions using `AND` logic. + """ + _and: [Product_Having!] + """ + Whether to apply DISTINCT to the aggregate function. + """ + _distinct: Boolean + """ + Negate the result of the provided Having condition. + """ + _not: Product_Having + """ + Apply multiple Having conditions using `OR` logic. + """ + _or: [Product_Having!] + """ + ✨ Generated from Field `Product`.`_count` of type `Int!` + """ + _count: Int_Filter + """ + ✨ Generated from Field `Product`.`descriptionEmbedding_count` of type `Int!` + """ + descriptionEmbedding_count: Int_Filter + """ + ✨ Generated from Field `Product`.`description_count` of type `Int!` + """ + description_count: Int_Filter + """ + ✨ Generated from Field `Product`.`id_count` of type `Int!` + """ + id_count: Int_Filter + """ + ✨ Generated from Field `Product`.`nameEmbedding_count` of type `Int!` + """ + nameEmbedding_count: Int_Filter + """ + ✨ Generated from Field `Product`.`name_count` of type `Int!` + """ + name_count: Int_Filter + """ + ✨ Generated from Field `Product`.`price_count` of type `Int!` + """ + price_count: Int_Filter + """ + ✨ Generated from Field `Product`.`productID_count` of type `Int!` + """ + productID_count: Int_Filter + """ + ✨ Generated from Field `Product`.`productSlug_count` of type `Int!` + """ + productSlug_count: Int_Filter + """ + ✨ Generated from Field `Product`.`price_sum` of type `Float` + """ + price_sum: Float_Filter + """ + ✨ Generated from Field `Product`.`price_avg` of type `Float` + """ + price_avg: Float_Filter + """ + ✨ Generated from Field `Product`.`price_min` of type `Float` + """ + price_min: Float_Filter + """ + ✨ Generated from Field `Product`.`price_max` of type `Float` + """ + price_max: Float_Filter +} +""" ✨ Generated key input type for table 'Product'. It represents the primary key fields used to uniquely identify a row in the table. """ input Product_Key { @@ -199,6 +280,58 @@ input Product_Order { ✨ Generated from Field `Product`.`productSlug` of type `String!` """ productSlug: OrderDirection + """ + ✨ Generated from Field `Product`.`_count` of type `Int!` + """ + _count: OrderDirection + """ + ✨ Generated from Field `Product`.`descriptionEmbedding_count` of type `Int!` + """ + descriptionEmbedding_count: OrderDirection + """ + ✨ Generated from Field `Product`.`description_count` of type `Int!` + """ + description_count: OrderDirection + """ + ✨ Generated from Field `Product`.`id_count` of type `Int!` + """ + id_count: OrderDirection + """ + ✨ Generated from Field `Product`.`nameEmbedding_count` of type `Int!` + """ + nameEmbedding_count: OrderDirection + """ + ✨ Generated from Field `Product`.`name_count` of type `Int!` + """ + name_count: OrderDirection + """ + ✨ Generated from Field `Product`.`price_count` of type `Int!` + """ + price_count: OrderDirection + """ + ✨ Generated from Field `Product`.`productID_count` of type `Int!` + """ + productID_count: OrderDirection + """ + ✨ Generated from Field `Product`.`productSlug_count` of type `Int!` + """ + productSlug_count: OrderDirection + """ + ✨ Generated from Field `Product`.`price_sum` of type `Float` + """ + price_sum: OrderDirection + """ + ✨ Generated from Field `Product`.`price_avg` of type `Float` + """ + price_avg: OrderDirection + """ + ✨ Generated from Field `Product`.`price_min` of type `Float` + """ + price_min: OrderDirection + """ + ✨ Generated from Field `Product`.`price_max` of type `Float` + """ + price_max: OrderDirection } """ ✨ Generated data input type for table 'Review'. It includes all necessary fields for creating or upserting rows into table. @@ -241,6 +374,10 @@ input Review_Data { """ createdAt_expr: Date_Expr """ + ✨ `_update` server value variant of `createdAt` (✨ Generated from Field `Review`.`createdAt` of type `Date!`) + """ + createdAt_update: [Date_Update!] + """ ✨ Generated from Field `Review`.`productID` of type `String!` """ productID: String @@ -269,6 +406,14 @@ input Review_Data { """ rating: Float """ + ✨ `_expr` server value variant of `rating` (✨ Generated from Field `Review`.`rating` of type `Float!`) + """ + rating_expr: Float_Expr + """ + ✨ `_update` server value variant of `rating` (✨ Generated from Field `Review`.`rating` of type `Float!`) + """ + rating_update: [Float_Update!] + """ ✨ Generated from Field `Review`.`userID` of type `String!` """ userID: String @@ -356,6 +501,95 @@ input Review_FirstRow { where: Review_Filter } """ +✨ Generated having input type for table 'Review'. This input allows you to filter groups during aggregate queries using various conditions. Use `_or`, `_and`, and `_not` to compose complex filters. +""" +input Review_Having { + """ + Apply multiple Having conditions using `AND` logic. + """ + _and: [Review_Having!] + """ + Whether to apply DISTINCT to the aggregate function. + """ + _distinct: Boolean + """ + Negate the result of the provided Having condition. + """ + _not: Review_Having + """ + Apply multiple Having conditions using `OR` logic. + """ + _or: [Review_Having!] + """ + ✨ Generated from Field `Review`.`_count` of type `Int!` + """ + _count: Int_Filter + """ + ✨ Generated from Field `Review`.`contentEmbedding_count` of type `Int!` + """ + contentEmbedding_count: Int_Filter + """ + ✨ Generated from Field `Review`.`content_count` of type `Int!` + """ + content_count: Int_Filter + """ + ✨ Generated from Field `Review`.`createdAt_count` of type `Int!` + """ + createdAt_count: Int_Filter + """ + ✨ Generated from Field `Review`.`id_count` of type `Int!` + """ + id_count: Int_Filter + """ + ✨ Generated from Field `Review`.`productID_count` of type `Int!` + """ + productID_count: Int_Filter + """ + ✨ Generated from Field `Review`.`productName_count` of type `Int!` + """ + productName_count: Int_Filter + """ + ✨ Generated from Field `Review`.`productSlug_count` of type `Int!` + """ + productSlug_count: Int_Filter + """ + ✨ Generated from Field `Review`.`rating_count` of type `Int!` + """ + rating_count: Int_Filter + """ + ✨ Generated from Field `Review`.`userID_count` of type `Int!` + """ + userID_count: Int_Filter + """ + ✨ Generated from Field `Review`.`userName_count` of type `Int!` + """ + userName_count: Int_Filter + """ + ✨ Generated from Field `Review`.`rating_sum` of type `Float` + """ + rating_sum: Float_Filter + """ + ✨ Generated from Field `Review`.`rating_avg` of type `Float` + """ + rating_avg: Float_Filter + """ + ✨ Generated from Field `Review`.`createdAt_min` of type `Date` + """ + createdAt_min: Date_Filter + """ + ✨ Generated from Field `Review`.`rating_min` of type `Float` + """ + rating_min: Float_Filter + """ + ✨ Generated from Field `Review`.`createdAt_max` of type `Date` + """ + createdAt_max: Date_Filter + """ + ✨ Generated from Field `Review`.`rating_max` of type `Float` + """ + rating_max: Float_Filter +} +""" ✨ Generated key input type for table 'Review'. It represents the primary key fields used to uniquely identify a row in the table. """ input Review_Key { @@ -425,4 +659,72 @@ input Review_Order { ✨ Generated from Field `Review`.`userName` of type `String!` """ userName: OrderDirection + """ + ✨ Generated from Field `Review`.`_count` of type `Int!` + """ + _count: OrderDirection + """ + ✨ Generated from Field `Review`.`contentEmbedding_count` of type `Int!` + """ + contentEmbedding_count: OrderDirection + """ + ✨ Generated from Field `Review`.`content_count` of type `Int!` + """ + content_count: OrderDirection + """ + ✨ Generated from Field `Review`.`createdAt_count` of type `Int!` + """ + createdAt_count: OrderDirection + """ + ✨ Generated from Field `Review`.`id_count` of type `Int!` + """ + id_count: OrderDirection + """ + ✨ Generated from Field `Review`.`productID_count` of type `Int!` + """ + productID_count: OrderDirection + """ + ✨ Generated from Field `Review`.`productName_count` of type `Int!` + """ + productName_count: OrderDirection + """ + ✨ Generated from Field `Review`.`productSlug_count` of type `Int!` + """ + productSlug_count: OrderDirection + """ + ✨ Generated from Field `Review`.`rating_count` of type `Int!` + """ + rating_count: OrderDirection + """ + ✨ Generated from Field `Review`.`userID_count` of type `Int!` + """ + userID_count: OrderDirection + """ + ✨ Generated from Field `Review`.`userName_count` of type `Int!` + """ + userName_count: OrderDirection + """ + ✨ Generated from Field `Review`.`rating_sum` of type `Float` + """ + rating_sum: OrderDirection + """ + ✨ Generated from Field `Review`.`rating_avg` of type `Float` + """ + rating_avg: OrderDirection + """ + ✨ Generated from Field `Review`.`createdAt_min` of type `Date` + """ + createdAt_min: OrderDirection + """ + ✨ Generated from Field `Review`.`rating_min` of type `Float` + """ + rating_min: OrderDirection + """ + ✨ Generated from Field `Review`.`createdAt_max` of type `Date` + """ + createdAt_max: OrderDirection + """ + ✨ Generated from Field `Review`.`rating_max` of type `Float` + """ + rating_max: OrderDirection } diff --git a/starters/nextjs/shopify-ecommerce/dataconnect/.dataconnect/schema/main/mutation.gql b/starters/nextjs/shopify-ecommerce/dataconnect/.dataconnect/schema/main/mutation.gql index e6047c25..307ef99f 100644 --- a/starters/nextjs/shopify-ecommerce/dataconnect/.dataconnect/schema/main/mutation.gql +++ b/starters/nextjs/shopify-ecommerce/dataconnect/.dataconnect/schema/main/mutation.gql @@ -1,6 +1,6 @@ extend type Mutation { """ - ✨ Insert a single `Product` into the table. Columns not specified in `data` will receive defaults (e.g. `null`). + ✨ Insert a single `Product` into the table and return its key. Columns not specified in `data` will receive defaults (e.g. `null`). """ product_insert( """ @@ -9,7 +9,7 @@ extend type Mutation { data: Product_Data! ): Product_KeyOutput! @fdc_generated(from: "Product", purpose: INSERT_SINGLE) """ - ✨ Insert a single `Review` into the table. Columns not specified in `data` will receive defaults (e.g. `null`). + ✨ Insert a single `Review` into the table and return its key. Columns not specified in `data` will receive defaults (e.g. `null`). """ review_insert( """ @@ -18,7 +18,7 @@ extend type Mutation { data: Review_Data! ): Review_KeyOutput! @fdc_generated(from: "Review", purpose: INSERT_SINGLE) """ - ✨ Insert `Product` objects into the table. Columns not specified in `data` will receive defaults (e.g. `null`). + ✨ Insert `Product` objects into the table and return their keys. Columns not specified in `data` will receive defaults (e.g. `null`). """ product_insertMany( """ @@ -27,7 +27,7 @@ extend type Mutation { data: [Product_Data!]! ): [Product_KeyOutput!]! @fdc_generated(from: "Product", purpose: INSERT_MULTIPLE) """ - ✨ Insert `Review` objects into the table. Columns not specified in `data` will receive defaults (e.g. `null`). + ✨ Insert `Review` objects into the table and return their keys. Columns not specified in `data` will receive defaults (e.g. `null`). """ review_insertMany( """ @@ -36,7 +36,7 @@ extend type Mutation { data: [Review_Data!]! ): [Review_KeyOutput!]! @fdc_generated(from: "Review", purpose: INSERT_MULTIPLE) """ - ✨ Insert or update a single `Product` into the table, based on the primary key. Returns the key of the newly inserted `Product`. + ✨ Insert or update a single `Product` into the table, based on the primary key. Returns the key of the newly inserted or existing updated `Product`. """ product_upsert( """ @@ -45,7 +45,7 @@ extend type Mutation { data: Product_Data! ): Product_KeyOutput! @fdc_generated(from: "Product", purpose: UPSERT_SINGLE) """ - ✨ Insert or update a single `Review` into the table, based on the primary key. Returns the key of the newly inserted `Review`. + ✨ Insert or update a single `Review` into the table, based on the primary key. Returns the key of the newly inserted or existing updated `Review`. """ review_upsert( """ @@ -54,7 +54,7 @@ extend type Mutation { data: Review_Data! ): Review_KeyOutput! @fdc_generated(from: "Review", purpose: UPSERT_SINGLE) """ - ✨ Insert or update `Product` objects into the table, based on the primary key. Returns the key of the newly inserted `Product`. + ✨ Insert or update `Product` objects into the table, based on the primary key. Returns the key of the newly inserted or existing updated `Product`. """ product_upsertMany( """ @@ -63,7 +63,7 @@ extend type Mutation { data: [Product_Data!]! ): [Product_KeyOutput!]! @fdc_generated(from: "Product", purpose: UPSERT_MULTIPLE) """ - ✨ Insert or update `Review` objects into the table, based on the primary key. Returns the key of the newly inserted `Review`. + ✨ Insert or update `Review` objects into the table, based on the primary key. Returns the key of the newly inserted or existing updated `Review`. """ review_upsertMany( """ @@ -72,7 +72,7 @@ extend type Mutation { data: [Review_Data!]! ): [Review_KeyOutput!]! @fdc_generated(from: "Review", purpose: UPSERT_MULTIPLE) """ - ✨ Update a single `Product` based on `id`, `key` or `first`, setting columns specified in `data`. Returns `null` if not found. + ✨ Update a single `Product` based on `id`, `key` or `first`, setting columns specified in `data`. Returns the key of the updated `Product` or `null` if not found. """ product_update( """ @@ -96,7 +96,7 @@ extend type Mutation { data: Product_Data! ): Product_KeyOutput @fdc_generated(from: "Product", purpose: UPDATE_SINGLE) """ - ✨ Update a single `Review` based on `id`, `key` or `first`, setting columns specified in `data`. Returns `null` if not found. + ✨ Update a single `Review` based on `id`, `key` or `first`, setting columns specified in `data`. Returns the key of the updated `Review` or `null` if not found. """ review_update( """ diff --git a/starters/nextjs/shopify-ecommerce/dataconnect/.dataconnect/schema/main/query.gql b/starters/nextjs/shopify-ecommerce/dataconnect/.dataconnect/schema/main/query.gql index 9483d11f..04250e08 100644 --- a/starters/nextjs/shopify-ecommerce/dataconnect/.dataconnect/schema/main/query.gql +++ b/starters/nextjs/shopify-ecommerce/dataconnect/.dataconnect/schema/main/query.gql @@ -38,7 +38,7 @@ extend type Query { first: Review_FirstRow ): Review @fdc_generated(from: "Review", purpose: QUERY_SINGLE) """ - ✨ List `Product` objects in the table, optionally filtered by `where` conditions. + ✨ List `Product` objects in the table and return selected fields, optionally filtered by `where` conditions """ products( """ @@ -60,9 +60,19 @@ extend type Query { Maximum number of rows to return (defaults to 100 rows). """ limit: Int = 100 + + """ + Set to true to return distinct results. + """ + distinct: Boolean = false + + """ + Filter condition to apply to the groups of aggregate queries. + """ + having: Product_Having ): [Product!]! @fdc_generated(from: "Product", purpose: QUERY_MULTIPLE) """ - ✨ List `Review` objects in the table, optionally filtered by `where` conditions. + ✨ List `Review` objects in the table and return selected fields, optionally filtered by `where` conditions """ reviews( """ @@ -84,9 +94,19 @@ extend type Query { Maximum number of rows to return (defaults to 100 rows). """ limit: Int = 100 + + """ + Set to true to return distinct results. + """ + distinct: Boolean = false + + """ + Filter condition to apply to the groups of aggregate queries. + """ + having: Review_Having ): [Review!]! @fdc_generated(from: "Review", purpose: QUERY_MULTIPLE) """ - ✨ List `Product` objects ordered by vector similarity between the `descriptionEmbedding` field and `compare_embed`. + ✨ List `Product` objects and return selected fields, ordered by vector similarity between the `descriptionEmbedding` field and `compare_embed`. (Alternatively, `compare` can be used if the input is a raw Vector.) """ products_descriptionEmbedding_similarity( @@ -121,7 +141,7 @@ extend type Query { limit: Int = 100 ): [Product!]! @fdc_generated(from: "Product.descriptionEmbedding", purpose: QUERY_MULTIPLE_BY_SIMILARITY) """ - ✨ List `Product` objects ordered by vector similarity between the `nameEmbedding` field and `compare_embed`. + ✨ List `Product` objects and return selected fields, ordered by vector similarity between the `nameEmbedding` field and `compare_embed`. (Alternatively, `compare` can be used if the input is a raw Vector.) """ products_nameEmbedding_similarity( @@ -156,7 +176,7 @@ extend type Query { limit: Int = 100 ): [Product!]! @fdc_generated(from: "Product.nameEmbedding", purpose: QUERY_MULTIPLE_BY_SIMILARITY) """ - ✨ List `Review` objects ordered by vector similarity between the `contentEmbedding` field and `compare_embed`. + ✨ List `Review` objects and return selected fields, ordered by vector similarity between the `contentEmbedding` field and `compare_embed`. (Alternatively, `compare` can be used if the input is a raw Vector.) """ reviews_contentEmbedding_similarity( diff --git a/starters/nextjs/shopify-ecommerce/dataconnect/.dataconnect/schema/main/relation.gql b/starters/nextjs/shopify-ecommerce/dataconnect/.dataconnect/schema/main/relation.gql new file mode 100644 index 00000000..981266ff --- /dev/null +++ b/starters/nextjs/shopify-ecommerce/dataconnect/.dataconnect/schema/main/relation.gql @@ -0,0 +1,242 @@ +extend type Product { + """ + Implicit metadata field that cannot be written. It provides extra information about query results. + """ + _metadata: _Metadata @fdc_generated(from: "Product", purpose: METADATA_FIELD) + """ + ✨ Count the number of rows in the `Product` table. + """ + _count: Int! @fdc_generated(from: "Product.", purpose: QUERY_COUNT) + """ + ✨ Count the number of rows in the `Product` table where the `descriptionEmbedding` field is non-null. Pass the `distinct` argument to instead count the number of distinct values. + """ + descriptionEmbedding_count( + """ + Set to true to count the number of distinct values. + """ + distinct: Boolean = false + ): Int! @fdc_generated(from: "Product.descriptionEmbedding", purpose: QUERY_COUNT) + """ + ✨ Count the number of rows in the `Product` table where the `description` field is non-null. Pass the `distinct` argument to instead count the number of distinct values. + """ + description_count( + """ + Set to true to count the number of distinct values. + """ + distinct: Boolean = false + ): Int! @fdc_generated(from: "Product.description", purpose: QUERY_COUNT) + """ + ✨ Count the number of rows in the `Product` table where the `id` field is non-null. Pass the `distinct` argument to instead count the number of distinct values. + """ + id_count( + """ + Set to true to count the number of distinct values. + """ + distinct: Boolean = false + ): Int! @fdc_generated(from: "Product.id", purpose: QUERY_COUNT) + """ + ✨ Count the number of rows in the `Product` table where the `nameEmbedding` field is non-null. Pass the `distinct` argument to instead count the number of distinct values. + """ + nameEmbedding_count( + """ + Set to true to count the number of distinct values. + """ + distinct: Boolean = false + ): Int! @fdc_generated(from: "Product.nameEmbedding", purpose: QUERY_COUNT) + """ + ✨ Count the number of rows in the `Product` table where the `name` field is non-null. Pass the `distinct` argument to instead count the number of distinct values. + """ + name_count( + """ + Set to true to count the number of distinct values. + """ + distinct: Boolean = false + ): Int! @fdc_generated(from: "Product.name", purpose: QUERY_COUNT) + """ + ✨ Count the number of rows in the `Product` table where the `price` field is non-null. Pass the `distinct` argument to instead count the number of distinct values. + """ + price_count( + """ + Set to true to count the number of distinct values. + """ + distinct: Boolean = false + ): Int! @fdc_generated(from: "Product.price", purpose: QUERY_COUNT) + """ + ✨ Count the number of rows in the `Product` table where the `productID` field is non-null. Pass the `distinct` argument to instead count the number of distinct values. + """ + productID_count( + """ + Set to true to count the number of distinct values. + """ + distinct: Boolean = false + ): Int! @fdc_generated(from: "Product.productID", purpose: QUERY_COUNT) + """ + ✨ Count the number of rows in the `Product` table where the `productSlug` field is non-null. Pass the `distinct` argument to instead count the number of distinct values. + """ + productSlug_count( + """ + Set to true to count the number of distinct values. + """ + distinct: Boolean = false + ): Int! @fdc_generated(from: "Product.productSlug", purpose: QUERY_COUNT) + """ + ✨ Sum the `price` field in the `Product` table. + """ + price_sum( + """ + Set to true to sum the distinct values. + """ + distinct: Boolean = false + ): Float @fdc_generated(from: "Product.price", purpose: QUERY_SUM) + """ + ✨ Average the `price` field in the `Product` table. + """ + price_avg( + """ + Set to true to average the distinct values. + """ + distinct: Boolean = false + ): Float @fdc_generated(from: "Product.price", purpose: QUERY_AVG) + """ + ✨ Minimum of the `price` field in the `Product` table. + """ + price_min: Float @fdc_generated(from: "Product.price", purpose: QUERY_MIN) + """ + ✨ Maximum of the `price` field in the `Product` table. + """ + price_max: Float @fdc_generated(from: "Product.price", purpose: QUERY_MAX) +} +extend type Review { + """ + Implicit metadata field that cannot be written. It provides extra information about query results. + """ + _metadata: _Metadata @fdc_generated(from: "Review", purpose: METADATA_FIELD) + """ + ✨ Count the number of rows in the `Review` table. + """ + _count: Int! @fdc_generated(from: "Review.", purpose: QUERY_COUNT) + """ + ✨ Count the number of rows in the `Review` table where the `contentEmbedding` field is non-null. Pass the `distinct` argument to instead count the number of distinct values. + """ + contentEmbedding_count( + """ + Set to true to count the number of distinct values. + """ + distinct: Boolean = false + ): Int! @fdc_generated(from: "Review.contentEmbedding", purpose: QUERY_COUNT) + """ + ✨ Count the number of rows in the `Review` table where the `content` field is non-null. Pass the `distinct` argument to instead count the number of distinct values. + """ + content_count( + """ + Set to true to count the number of distinct values. + """ + distinct: Boolean = false + ): Int! @fdc_generated(from: "Review.content", purpose: QUERY_COUNT) + """ + ✨ Count the number of rows in the `Review` table where the `createdAt` field is non-null. Pass the `distinct` argument to instead count the number of distinct values. + """ + createdAt_count( + """ + Set to true to count the number of distinct values. + """ + distinct: Boolean = false + ): Int! @fdc_generated(from: "Review.createdAt", purpose: QUERY_COUNT) + """ + ✨ Count the number of rows in the `Review` table where the `id` field is non-null. Pass the `distinct` argument to instead count the number of distinct values. + """ + id_count( + """ + Set to true to count the number of distinct values. + """ + distinct: Boolean = false + ): Int! @fdc_generated(from: "Review.id", purpose: QUERY_COUNT) + """ + ✨ Count the number of rows in the `Review` table where the `productID` field is non-null. Pass the `distinct` argument to instead count the number of distinct values. + """ + productID_count( + """ + Set to true to count the number of distinct values. + """ + distinct: Boolean = false + ): Int! @fdc_generated(from: "Review.productID", purpose: QUERY_COUNT) + """ + ✨ Count the number of rows in the `Review` table where the `productName` field is non-null. Pass the `distinct` argument to instead count the number of distinct values. + """ + productName_count( + """ + Set to true to count the number of distinct values. + """ + distinct: Boolean = false + ): Int! @fdc_generated(from: "Review.productName", purpose: QUERY_COUNT) + """ + ✨ Count the number of rows in the `Review` table where the `productSlug` field is non-null. Pass the `distinct` argument to instead count the number of distinct values. + """ + productSlug_count( + """ + Set to true to count the number of distinct values. + """ + distinct: Boolean = false + ): Int! @fdc_generated(from: "Review.productSlug", purpose: QUERY_COUNT) + """ + ✨ Count the number of rows in the `Review` table where the `rating` field is non-null. Pass the `distinct` argument to instead count the number of distinct values. + """ + rating_count( + """ + Set to true to count the number of distinct values. + """ + distinct: Boolean = false + ): Int! @fdc_generated(from: "Review.rating", purpose: QUERY_COUNT) + """ + ✨ Count the number of rows in the `Review` table where the `userID` field is non-null. Pass the `distinct` argument to instead count the number of distinct values. + """ + userID_count( + """ + Set to true to count the number of distinct values. + """ + distinct: Boolean = false + ): Int! @fdc_generated(from: "Review.userID", purpose: QUERY_COUNT) + """ + ✨ Count the number of rows in the `Review` table where the `userName` field is non-null. Pass the `distinct` argument to instead count the number of distinct values. + """ + userName_count( + """ + Set to true to count the number of distinct values. + """ + distinct: Boolean = false + ): Int! @fdc_generated(from: "Review.userName", purpose: QUERY_COUNT) + """ + ✨ Sum the `rating` field in the `Review` table. + """ + rating_sum( + """ + Set to true to sum the distinct values. + """ + distinct: Boolean = false + ): Float @fdc_generated(from: "Review.rating", purpose: QUERY_SUM) + """ + ✨ Average the `rating` field in the `Review` table. + """ + rating_avg( + """ + Set to true to average the distinct values. + """ + distinct: Boolean = false + ): Float @fdc_generated(from: "Review.rating", purpose: QUERY_AVG) + """ + ✨ Minimum of the `createdAt` field in the `Review` table. + """ + createdAt_min: Date @fdc_generated(from: "Review.createdAt", purpose: QUERY_MIN) + """ + ✨ Minimum of the `rating` field in the `Review` table. + """ + rating_min: Float @fdc_generated(from: "Review.rating", purpose: QUERY_MIN) + """ + ✨ Maximum of the `createdAt` field in the `Review` table. + """ + createdAt_max: Date @fdc_generated(from: "Review.createdAt", purpose: QUERY_MAX) + """ + ✨ Maximum of the `rating` field in the `Review` table. + """ + rating_max: Float @fdc_generated(from: "Review.rating", purpose: QUERY_MAX) +} diff --git a/starters/nextjs/shopify-ecommerce/dataconnect/.dataconnect/schema/prelude.gql b/starters/nextjs/shopify-ecommerce/dataconnect/.dataconnect/schema/prelude.gql index bc21c044..28894fd7 100644 --- a/starters/nextjs/shopify-ecommerce/dataconnect/.dataconnect/schema/prelude.gql +++ b/starters/nextjs/shopify-ecommerce/dataconnect/.dataconnect/schema/prelude.gql @@ -1,5 +1,5 @@ "AccessLevel specifies coarse access policies for common situations." -enum AccessLevel { +enum AccessLevel @fdc_forbiddenAsVariableType @fdc_forbiddenAsFieldType { """ This operation is accessible to anyone, with or without authentication. Equivalent to: `@auth(expr: "true")` @@ -54,20 +54,36 @@ directive @auth( Exactly one of `level` and `expr` should be specified. """ expr: Boolean_Expr @fdc_oneOf(required: true) + """ + If the `@auth` on this operation is considered insecure, then developer + acknowledgement is required to deploy this operation, for new operations. + `@auth` is considered insecure if `level: PUBLIC`, or if + `level: USER/USER_ANON/USER_EMAIL_VERIFIED` and `auth.uid` is not referenced + in the operation. + If `insecureReason` is set, no further developer acknowledgement is needed. + """ + insecureReason: String ) on QUERY | MUTATION - """ Require that this mutation always run in a DB transaction. Mutations with `@transaction` are guaranteed to either fully succeed or fully -fail. If any of the fields within the transaction fails, the entire transaction -is rolled back. From a client standpoint, any failure behaves as if the entire -request had failed with a request error and execution had not begun. +fail. Upon the first error in a transaction (either an execution error or failed +`@check`), the transaction will be rolled back. In the GraphQL response, all +fields within the transaction will be `null`, each with an error raised. + +- Fields that have been already evaluated will be nullified due to the rollback + and a "(rolled back)" error will be reported on each of them. +- The execution error or failed `@check` will be reported on the current field. +- Subsequent fields will not be executed. An `(aborted)` error will be reported + on each subsequent field. Mutations without `@transaction` would execute each root field one after -another in sequence. It surfaces any errors as partial [field errors](https://spec.graphql.org/October2021/#sec-Errors.Field-errors), -but not impacts the subsequent executions. +another in sequence. They surface any errors as partial +[field errors](https://spec.graphql.org/October2021/#sec-Errors.Field-errors), +but does not impact the execution of subsequent fields. However, failed +`@check`s still terminate the entire operation. The `@transaction` directive cannot be added to queries for now. Currently, queries cannot fail partially, the response data is not guaranteed @@ -75,6 +91,75 @@ to be a consistent snapshot. """ directive @transaction on MUTATION +""" +Redact a part of the response from the client. + +Redacted fields are still evaluated for side effects (including data changes and +`@check`) and the results are still available to later steps in CEL expressions +(via `response.fieldName`). +""" +directive @redact on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT + +""" +Ensure this field is present and is not null or `[]`, or abort the request / transaction. + +A CEL expression, `expr` is used to test the field value. It defaults to +rejecting null and `[]` but a custom expression can be provided instead. + +If the field occurs multiple times (i.e. directly or indirectly nested under a +list), `expr` will be executed once for each occurrence and `@check` succeeds if +all values succeed. `@check` fails when the field is not present at all (i.e. +all ancestor paths contain `null` or `[]`), unless `optional` is true. + +If a `@check` fails in a mutation, the top-level field containing it will be +replaced with a partial error, whose message can be customzied via the `message` +argument. Each subsequent top-level fields will return an aborted error (i.e. +not executed). To rollback previous steps, see `@transaction`. +""" +directive @check( + """ + The CEL expression to test the field value (or values if nested under a list). + + Within the CEL expression, a special value `this` evaluates to the field that + this directive is attached to. If this field occurs multiple times because + any ancestor is a list, each occurrence is tested with `this` bound to each + value. When the field itself is a list or object, `this` follows the same + structure (including all descendants selected in case of objects). + + For any given path, if an ancestor is `null` or `[]`, the field will not be + reached and the CEL evaluation will be skipped for that path. In other words, + evaluation only takes place when `this` is `null` or non-null, but never + undefined. (See also the `optional` argument.) + """ + expr: Boolean_Expr! = "!(this in [null, []])" + """ + The error message to return to the client if the check fails. + + Defaults to "permission denied" if not specified. + """ + message: String! = "permission denied" + """ + Whether the check should pass or fail (default) when the field is not present. + + A field will not be reached at a given path if its parent or any ancestor is + `[]` or `null`. When this happens to all paths, the field will not be present + anywhere in the response tree. In other words, `expr` is evaluated 0 times. + By default, @check will automatically fail in this case. Set this argument to + `true` to make it pass even if no tests are run (a.k.a. "vacuously true"). + """ + optional: Boolean = false +) repeatable on QUERY | MUTATION | FIELD | FRAGMENT_DEFINITION | FRAGMENT_SPREAD | INLINE_FRAGMENT + +""" +Marks an element of a GraphQL operation as no longer supported for client use. +The Firebase Data Connect backend will continue supporting this element, +but it will no longer be visible in the generated SDKs. +""" +directive @retired( + "Provides the reason for retirement." + reason: String +) on QUERY | MUTATION | FIELD | VARIABLE_DEFINITION + "Query filter criteria for `String` scalar fields." input String_Filter { "When true, match if field `IS NULL`. When false, match if field is `NOT NULL`." @@ -120,6 +205,25 @@ input String_Filter { `LIKE '%value'` """ endsWith: String + """ + Match based on the provided pattern. + """ + pattern: String_Pattern +} + +input String_Pattern { + """ + Match using LIKE semantics (https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-LIKE) + """ + like: String @fdc_oneOf + """ + Match against a POSIX regular expression. + """ + regex: String @fdc_oneOf + """ + If true, match patterns case-insensitively. + """ + ignoreCase: Boolean } "Query filter criteris for `[String!]` scalar fields." @@ -139,9 +243,19 @@ input UUID_Filter { "When true, match if field `IS NULL`. When false, match if field is `NOT NULL`." isNull: Boolean "Match if field is exactly equal to provided value." - eq: UUID + eq: UUID @fdc_oneOf(group: "eq") + """ + Match if field is exactly equal to the result of the provided server value + expression. + """ + eq_expr: UUID_Expr @fdc_oneOf(group: "eq") "Match if field is not equal to provided value." - ne: UUID + ne: UUID @fdc_oneOf(group: "ne") + """ + Match if field is not equal to the result of the provided server value + expression. + """ + ne_expr: UUID_Expr @fdc_oneOf(group: "ne") "Match if field value is among the provided list of values." in: [UUID!] "Match if field value is not among the provided list of values." @@ -165,21 +279,51 @@ input Int_Filter { "When true, match if field `IS NULL`. When false, match if field is `NOT NULL`." isNull: Boolean "Match if field is exactly equal to provided value." - eq: Int + eq: Int @fdc_oneOf(group: "eq") + """ + Match if field is exactly equal to the result of the provided server value + expression. + """ + eq_expr: Int_Expr @fdc_oneOf(group: "eq") "Match if field is not equal to provided value." - ne: Int + ne: Int @fdc_oneOf(group: "ne") + """ + Match if field is not equal to the result of the provided server value + expression. + """ + ne_expr: Int_Expr @fdc_oneOf(group: "ne") "Match if field value is among the provided list of values." in: [Int!] "Match if field value is not among the provided list of values." nin: [Int!] "Match if field value is greater than the provided value." - gt: Int + gt: Int @fdc_oneOf(group: "gt") + """ + Match if field value is greater than the result of the provided server value + expression. + """ + gt_expr: Int_Expr @fdc_oneOf(group: "gt") "Match if field value is greater than or equal to the provided value." - ge: Int + ge: Int @fdc_oneOf(group: "ge") + """ + Match if field value is greater than or equal to the result of the provided + server value expression. + """ + ge_expr: Int_Expr @fdc_oneOf(group: "ge") "Match if field value is less than the provided value." - lt: Int + lt: Int @fdc_oneOf(group: "lt") + """ + Match if field value is less than the result of the provided server value + expression. + """ + lt_expr: Int_Expr @fdc_oneOf(group: "lt") "Match if field value is less than or equal to the provided value." - le: Int + le: Int @fdc_oneOf(group: "le") + """ + Match if field value is less than or equal to the result of the provided + server value expression. + """ + le_expr: Int_Expr @fdc_oneOf(group: "le") } "Query filter criteris for `[Int!]` scalar fields." @@ -199,21 +343,51 @@ input Int64_Filter { "When true, match if field `IS NULL`. When false, match if field is `NOT NULL`." isNull: Boolean "Match if field is exactly equal to provided value." - eq: Int64 + eq: Int64 @fdc_oneOf(group: "eq") + """ + Match if field is exactly equal to the result of the provided server value + expression. + """ + eq_expr: Int64_Expr @fdc_oneOf(group: "eq") "Match if field is not equal to provided value." - ne: Int64 + ne: Int64 @fdc_oneOf(group: "ne") + """ + Match if field is not equal to the result of the provided server value + expression. + """ + ne_expr: Int64_Expr @fdc_oneOf(group: "ne") "Match if field value is among the provided list of values." in: [Int64!] "Match if field value is not among the provided list of values." nin: [Int64!] "Match if field value is greater than the provided value." - gt: Int64 + gt: Int64 @fdc_oneOf(group: "gt") + """ + Match if field value is greater than the result of the provided server value + expression. + """ + gt_expr: Int64_Expr @fdc_oneOf(group: "gt") "Match if field value is greater than or equal to the provided value." - ge: Int64 + ge: Int64 @fdc_oneOf(group: "ge") + """ + Match if field value is greater than or equal to the result of the provided + server value expression. + """ + ge_expr: Int64_Expr @fdc_oneOf(group: "ge") "Match if field value is less than the provided value." - lt: Int64 + lt: Int64 @fdc_oneOf(group: "lt") + """ + Match if field value is less than the result of the provided server value + expression. + """ + lt_expr: Int64_Expr @fdc_oneOf(group: "lt") "Match if field value is less than or equal to the provided value." - le: Int64 + le: Int64 @fdc_oneOf(group: "le") + """ + Match if field value is less than or equal to the result of the provided + server value expression. + """ + le_expr: Int64_Expr @fdc_oneOf(group: "le") } "Query filter criteria for `[Int64!]` scalar fields." @@ -233,21 +407,51 @@ input Float_Filter { "When true, match if field `IS NULL`. When false, match if field is `NOT NULL`." isNull: Boolean "Match if field is exactly equal to provided value." - eq: Float + eq: Float @fdc_oneOf(group: "eq") + """ + Match if field is exactly equal to the result of the provided server value + expression. + """ + eq_expr: Float_Expr @fdc_oneOf(group: "eq") "Match if field is not equal to provided value." - ne: Float + ne: Float @fdc_oneOf(group: "ne") + """ + Match if field is not equal to the result of the provided server value + expression. + """ + ne_expr: Float_Expr @fdc_oneOf(group: "ne") "Match if field value is among the provided list of values." in: [Float!] "Match if field value is not among the provided list of values." nin: [Float!] "Match if field value is greater than the provided value." - gt: Float + gt: Float @fdc_oneOf(group: "gt") + """ + Match if field value is greater than the result of the provided server value + expression. + """ + gt_expr: Float_Expr @fdc_oneOf(group: "gt") "Match if field value is greater than or equal to the provided value." - ge: Float + ge: Float @fdc_oneOf(group: "ge") + """ + Match if field value is greater than or equal to the result of the provided + server value expression. + """ + ge_expr: Float_Expr @fdc_oneOf(group: "ge") "Match if field value is less than the provided value." - lt: Float + lt: Float @fdc_oneOf(group: "lt") + """ + Match if field value is less than the result of the provided server value + expression. + """ + lt_expr: Float_Expr @fdc_oneOf(group: "lt") "Match if field value is less than or equal to the provided value." - le: Float + le: Float @fdc_oneOf(group: "le") + """ + Match if field value is less than or equal to the result of the provided + server value expression. + """ + le_expr: Float_Expr @fdc_oneOf(group: "le") } "Query filter criteria for `[Float!]` scalar fields." @@ -267,9 +471,15 @@ input Boolean_Filter { "When true, match if field `IS NULL`. When false, match if field is `NOT NULL`." isNull: Boolean "Match if field is exactly equal to provided value." - eq: Boolean + eq: Boolean @fdc_oneOf(group: "eq") + "Match if field is equal to the result of the provided expression." + eq_expr: Boolean_Expr @fdc_oneOf(group: "eq") "Match if field is not equal to provided value." - ne: Boolean + ne: Boolean @fdc_oneOf(group: "ne") + """ + Match if field does not match the result of the provided expression. + """ + ne_expr: Boolean_Expr @fdc_oneOf(group: "ne") "Match if field value is among the provided list of values." in: [Boolean!] "Match if field value is not among the provided list of values." @@ -293,9 +503,19 @@ input Any_Filter { "When true, match if field `IS NULL`. When false, match if field is `NOT NULL`." isNull: Boolean "Match if field is exactly equal to provided value." - eq: Any + eq: Any @fdc_oneOf(group: "eq") + """ + Match if field is exactly equal to the result of the provided server value + expression. + """ + eq_expr: Any_Expr @fdc_oneOf(group: "eq") "Match if field is not equal to provided value." - ne: Any + ne: Any @fdc_oneOf(group: "ne") + """ + Match if field is not equal to the result of the provided server value + expression. + """ + ne_expr: Any_Expr @fdc_oneOf(group: "ne") "Match if field value is among the provided list of values." in: [Any!] "Match if field value is not among the provided list of values." @@ -314,6 +534,50 @@ input Any_ListFilter { excludesAll: [Any!] } +""" +Mark a string field as searchable. +When this directive is added, the field will be indexed for full-text search, +and a _search field will be generated at the query root. +This directive accepts a `language` argument that defaults to `"english"` in +case no value is specified. +See: +- go/fdc-full-text-search +""" +directive @searchable( + """ + Language of the fields that you are searching over can be specified here + (e.g. "french", "spanish", etc.). + Defaults to "english" if not specified. + """ + language: String = "english") on FIELD_DEFINITION + +extend type _Metadata { + # During full text search, the relevance of the query term to this row. + # In other cases, this field is not set. + relevance: Float +} + + +enum Search_QueryFormat @fdc_forbiddenAsFieldType { + """ + Allows search engine style semantics (e.g. quoted strings, AND and OR). + """ + QUERY, + """ + Splits the query into words and does ANDs between them. + """ + PLAIN, + """ + Matches an exact phrase. Requires the words to be in the same order (i.e. "brown + dog" will not match "brown and red dog"). + """ + PHRASE, + """ + Create complex queries using the full set of tsquery operators. + """ + ADVANCED, +} + """ (Internal) A string that uniquely identifies a type, field, and so on. @@ -326,15 +590,28 @@ scalar SchemaCoordinate @fdc_forbiddenAsVariableType "(Internal) The purpose of a generated type or field." -enum GeneratedPurpose { +enum GeneratedPurpose @fdc_forbiddenAsVariableType @fdc_forbiddenAsFieldType { # Implicit fields added to the table types as columns. IMPLICIT_KEY_FIELD IMPLICIT_REF_FIELD + # Generated static fields extended to table types. + METADATA_FIELD + # Relational non-column fields extended to table types. QUERY_MULTIPLE_ONE_TO_MANY QUERY_MULTIPLE_MANY_TO_MANY + # Generated fields for aggregates + QUERY_COUNT + QUERY_SUM + QUERY_AVG + QUERY_MIN + QUERY_MAX + + # Generated field for full text search + QUERY_MULTIPLE_BY_FULL_TEXT_SEARCH + # Top-level Query fields. QUERY_SINGLE QUERY_MULTIPLE @@ -397,20 +674,6 @@ type _Doc { markdown: String! } -"(Internal) Added to things that may be removed from FDC and will soon be no longer usable in schema or operations." -directive @fdc_deprecated(reason: String = "No longer supported") on - | SCHEMA - | SCALAR - | OBJECT - | FIELD_DEFINITION - | ARGUMENT_DEFINITION - | INTERFACE - | UNION - | ENUM - | ENUM_VALUE - | INPUT_OBJECT - | INPUT_FIELD_DEFINITION - "(Internal) Added to scalars representing quoted CEL expressions." directive @fdc_celExpression( "The expected CEL type that the expression should evaluate to." @@ -445,6 +708,26 @@ directive @fdc_oneOf( required: Boolean! = false ) repeatable on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION +""" +The `_Metadata` type is used to return metadata about a field in a response. +""" +type _Metadata { + # During vector similarity search, the distance between the query vector and + # this row's vector. In other cases, this field is not set. + distance: Float +} + +type Mutation { + """ + Run a query during the mutation and add fields into the response. + + Example: foo: query { users { id } } will add a field foo: {users: [{id: "..."}, …]} into the response JSON. + + Note: Data fetched this way can be handy for permission checks. See @check. + """ + query: Query +} + """ `UUID` is a string of hexadecimal digits representing an RFC4122-compliant UUID. @@ -554,7 +837,7 @@ scalar Any @specifiedBy(url: "https://www.json.org/json-en.html") The `Void` scalar type represents the absence of any value. It is typically used in operations where no value is expected in return. """ -scalar Void +scalar Void @fdc_forbiddenAsVariableType @fdc_forbiddenAsFieldType """ The `True` scalar type only accepts the boolean value `true`. @@ -611,19 +894,50 @@ scalar UUID_Expr @fdc_example(value: "uuidV4()", description: "Generates a new random UUID (version 4) every time.") """ -A Common Expression Language (CEL) expression whose return type is unspecified. +A Common Expression Language (CEL) expression that returns a Int at runtime. +""" +scalar Int_Expr + @specifiedBy(url: "https://github.com/google/cel-spec") + @fdc_celExpression(returnType: "int") + @fdc_forbiddenAsVariableType + @fdc_forbiddenAsFieldType + @fdc_example(value: "2 * 4", description: "Evaluates to 8.") + @fdc_example(value: "vars.foo.size()", description: "Assuming `vars.foo` is a string, it will evaluate to the length of the string.") + + +""" +A Common Expression Language (CEL) expression that returns a Int64 at runtime. +""" +scalar Int64_Expr + @specifiedBy(url: "https://github.com/google/cel-spec") + @fdc_celExpression(returnType: "int64") + @fdc_forbiddenAsVariableType + @fdc_forbiddenAsFieldType + @fdc_example(value: "5000*1000*1000", description: "Evaluates to 5e9.") -**Limitation**: Only a limited set of expressions are currently supported for each -type. +""" +A Common Expression Language (CEL) expression that returns a Float at runtime. +""" +scalar Float_Expr + @specifiedBy(url: "https://github.com/google/cel-spec") + @fdc_celExpression(returnType: "float") + @fdc_forbiddenAsVariableType + @fdc_forbiddenAsFieldType + @fdc_example(value: "2.0 * 4.0", description: "Evaluates to 8.0.") + +""" +A Common Expression Language (CEL) expression whose return type is valid JSON. + +Examples: + - `{'A' : 'B'}` (Evaluates to a JSON object.) + - `['A', 'B']` (Evaluates to a JSON array.) + - `{'A' 1, 'B': [1, 2, {'foo': 'bar'}]}` (Nested JSON objects and arrays.) """ scalar Any_Expr @specifiedBy(url: "https://github.com/google/cel-spec") @fdc_celExpression @fdc_forbiddenAsVariableType @fdc_forbiddenAsFieldType - @fdc_example(value: "auth.uid", description: "The ID of the currently logged in user in Firebase Auth. (Errors if not logged in.)") - @fdc_example(value: "uuidV4()", description: "Generates a new random UUID version 4 (formatted as 32 lower-case hex digits without delimiters if result type is String).") - @fdc_example(value: "request.time", description: "The timestamp when the request is received (with microseconds precision).") """ A PostgreSQL value expression whose return type is unspecified. @@ -983,7 +1297,7 @@ type OneTable @table { ``` Data Connect generates the necessary foreign key constraint. -```graphql +```sql CREATE TABLE "public"."many_table" ( "id" uuid NOT NULL DEFAULT uuid_generate_v4(), "ref_field_id" uuid NOT NULL, @@ -1162,7 +1476,7 @@ directive @ref( ) on FIELD_DEFINITION "Defines the orderBy direction in a query." -enum OrderDirection { +enum OrderDirection @fdc_forbiddenAsFieldType { "Results are ordered in ascending order." ASC "Results are ordered in descending order." @@ -1263,7 +1577,7 @@ directive @index( ) repeatable on FIELD_DEFINITION | OBJECT "Specifies the sorting order for database indexes." -enum IndexFieldOrder { +enum IndexFieldOrder @fdc_forbiddenAsVariableType @fdc_forbiddenAsFieldType { "Sorts the field in ascending order (from lowest to highest)." ASC "Sorts the field in descending order (from highest to lowest)." @@ -1271,7 +1585,7 @@ enum IndexFieldOrder { } "Defines the type of index to be used in the database." -enum IndexType { +enum IndexType @fdc_forbiddenAsVariableType @fdc_forbiddenAsFieldType { "A general-purpose index type commonly used for sorting and searching." BTREE "Generalized Inverted Index, optimized for indexing composite values such as arrays." @@ -1516,59 +1830,46 @@ input Timestamp_ListFilter { excludesAll: [Timestamp!] } -"Update input of a `Date` value." +"Update input of a `Date` value. Only one of `inc` or `dec` may be specified." input Date_Update { - "Set the field to the provided date." - set: Date @fdc_oneOf(group: "set") - "Set the field to the provided date CEL expression." - set_expr: Date_Expr @fdc_oneOf(group: "set") - "Set the field to the provided relative date." - set_date: Date_Relative @fdc_oneOf(group: "set") + "Increment the field by a provided duration." + inc: Date_Duration @fdc_oneOf + "Decrement the field by a provided duration." + dec: Date_Duration @fdc_oneOf } -"Update input of a `Date` list value." +"Update input of a `Date` list value. Only one of `append`, `prepend`, `add`, or `remove` may be specified." input Date_ListUpdate { - "Replace the current list with the provided list of `Date` values." - set: [Date!] "Append the provided `Date` values to the existing list." - append: [Date!] + append: [Date!] @fdc_oneOf "Prepend the provided `Date` values to the existing list." - prepend: [Date!] - "Remove the date value at the specified index." - delete: Int - "The index of the list to perform updates." - i: Int - "Update the date value at the specified index." - update: Date + prepend: [Date!] @fdc_oneOf + "Append any `Date` values that do not already exist to the list." + add: [Date!] @fdc_oneOf + "Remove all occurrences of each `Date` from the list." + remove: [Date!] @fdc_oneOf } -"Update input of a `Timestamp` value." +"Update input of a `Timestamp` value. Only one of `inc` or `dec` may be specified." input Timestamp_Update { - "Set the field to the provided timestamp." - set: Timestamp @fdc_oneOf(group: "set") - "Set the field to the provided timestamp CEL expression." - set_expr: Timestamp_Expr @fdc_oneOf(group: "set") - "Set the field to the provided relative timestamp." - set_time: Timestamp_Relative @fdc_oneOf(group: "set") + "Increment the field by a provided duration." + inc: Timestamp_Duration @fdc_oneOf + "Decrement the field by a provided duration." + dec: Timestamp_Duration @fdc_oneOf } -"Update input of an `Timestamp` list value." +"Update input of an `Timestamp` list value. Only one of `append`, `prepend`, `add`, or `remove` may be specified." input Timestamp_ListUpdate { - "Replace the current list with the provided list of `Timestamp` values." - set: [Timestamp!] "Append the provided `Timestamp` values to the existing list." - append: [Timestamp!] + append: [Timestamp!] @fdc_oneOf "Prepend the provided `Timestamp` values to the existing list." - prepend: [Timestamp!] - "Remove the timestamp value at the specified index." - delete: Int - "The index of the list to perform updates." - i: Int - "Update the timestamp value at the specified index." - update: Timestamp + prepend: [Timestamp!] @fdc_oneOf + "Append any `Timestamp` values that do not already exist to the list." + add: [Timestamp!] @fdc_oneOf + "Remove all occurrences of each `Timestamp` from the list." + remove: [Timestamp!] @fdc_oneOf } - "A runtime-calculated `Timestamp` value relative to `now` or `at`." input Timestamp_Relative @fdc_forbiddenAsVariableType @fdc_forbiddenAsFieldType { "Match for the current time." @@ -1653,132 +1954,112 @@ enum Date_Interval @fdc_forbiddenAsFieldType { YEAR } -"Update input of a `String` value." -input String_Update { - "Set the field to a provided value." - set: String @fdc_oneOf(group: "set") - "Set the field to a provided server value expression." - set_expr: String_Expr @fdc_oneOf(group: "set") -} - -"Update input of a `String` list value." +"Update input of a `String` list value. Only one of `append`, `prepend`, `add`, or `remove` may be specified." input String_ListUpdate { - "Set the list with the provided values." - set: [String!] "Append the provided values to the existing list." - append: [String!] + append: [String!] @fdc_oneOf "Prepend the provided values to the existing list." - prepend: [String!] + prepend: [String!] @fdc_oneOf + "Append values that do not already exist to the list." + add: [String!] @fdc_oneOf + "Remove all occurrences of each value from the list." + remove: [String!] @fdc_oneOf } -"Update input of a `UUID` value." -input UUID_Update { - "Set the field to a provided UUID." - set: UUID @fdc_oneOf(group: "set") - "Set the field to a provided UUID expression." - set_expr: UUID_Expr @fdc_oneOf(group: "set") -} - -"Update input of an `ID` list value." +"Update input of an `ID` list value. Only one of `append`, `prepend`, `add`, or `remove` may be specified." input UUID_ListUpdate { - "Set the list with the provided list of UUIDs." - set: [UUID!] "Append the provided UUIDs to the existing list." - append: [UUID!] + append: [UUID!] @fdc_oneOf "Prepend the provided UUIDs to the existing list." - prepend: [UUID!] + prepend: [UUID!] @fdc_oneOf + "Append values that do not already exist to the list." + add: [UUID!] @fdc_oneOf + "Remove all occurrences of each value from the list." + remove: [UUID!] @fdc_oneOf } -"Update input of an `Int` value." +"Update input of an `Int` value. Only one of `inc` or `dec` may be specified." input Int_Update { - "Set the field to a provided value." - set: Int "Increment the field by a provided value." - inc: Int + inc: Int @fdc_oneOf "Decrement the field by a provided value." - dec: Int + dec: Int @fdc_oneOf } -"Update input of an `Int` list value." +"Update input of an `Int` list value. Only one of `append`, `prepend`, `add`, or `remove` may be specified." input Int_ListUpdate { - "Set the list with the provided values." - set: [Int!] "Append the provided list of values to the existing list." - append: [Int!] + append: [Int!] @fdc_oneOf "Prepend the provided list of values to the existing list." - prepend: [Int!] + prepend: [Int!] @fdc_oneOf + "Append values that do not already exist to the list." + add: [Int!] @fdc_oneOf + "Remove all occurrences of each value from the list." + remove: [Int!] @fdc_oneOf } -"Update input of an `Int64` value." +"Update input of an `Int64` value. Only one of `inc` or `dec` may be specified." input Int64_Update { - "Set the field to a provided value." - set: Int64 "Increment the field by a provided value." - inc: Int64 + inc: Int64 @fdc_oneOf "Decrement the field by a provided value." - dec: Int64 + dec: Int64 @fdc_oneOf } -"Update input of an `Int64` list value." +"Update input of an `Int64` list value. Only one of `append`, `prepend`, `add`, or `remove` may be specified." input Int64_ListUpdate { - "Replace the list with the provided values." - set: [Int64!] "Append the provided list of values to the existing list." - append: [Int64!] + append: [Int64!] @fdc_oneOf "Prepend the provided list of values to the existing list." - prepend: [Int64!] + prepend: [Int64!] @fdc_oneOf + "Append values that do not already exist to the list." + add: [Int64!] @fdc_oneOf + "Remove all occurrences of each value from the list." + remove: [Int64!] @fdc_oneOf } -"Update input of a `Float` value." +"Update input of a `Float` value. Only one of `inc` or `dec` may be specified." input Float_Update { - "Set the field to a provided value." - set: Float "Increment the field by a provided value." - inc: Float + inc: Float @fdc_oneOf "Decrement the field by a provided value." - dec: Float + dec: Float @fdc_oneOf } -"Update input of a `Float` list value." +"Update input of a `Float` list value. Only one of `append`, `prepend`, `add`, or `remove` may be specified." input Float_ListUpdate { - "Set the list with the provided values." - set: [Float!] "Append the provided list of values to the existing list." - append: [Float!] + append: [Float!] @fdc_oneOf "Prepend the provided list of values to the existing list." - prepend: [Float!] -} - -"Update input of a `Boolean` value." -input Boolean_Update { - "Set the field to a provided value." - set: Boolean + prepend: [Float!] @fdc_oneOf + "Append values that do not already exist to the list." + add: [Float!] @fdc_oneOf + "Remove all occurrences of each value from the list." + remove: [Float!] @fdc_oneOf } -"Update input of a `Boolean` list value." +"Update input of a `Boolean` list value. Only one of `append`, `prepend`, `add`, or `remove` may be specified." input Boolean_ListUpdate { - "Set the list with the provided values." - set: [Boolean!] "Append the provided list of values to the existing list." - append: [Boolean!] + append: [Boolean!] @fdc_oneOf "Prepend the provided list of values to the existing list." - prepend: [Boolean!] -} - -"Update input of an `Any` value." -input Any_Update { - "Set the field to a provided value." - set: Any + prepend: [Boolean!] @fdc_oneOf + "Append values that do not already exist to the list." + add: [Boolean!] @fdc_oneOf + "Remove all occurrences of each value from the list." + remove: [Boolean!] @fdc_oneOf } -"Update input of an `Any` list value." +"Update input of an `Any` list value. Only one of `append`, `prepend`, `add`, or `remove` may be specified." input Any_ListUpdate { - "Set the list with the provided values." - set: [Any!] "Append the provided list of values to the existing list." - append: [Any!] + append: [Any!] @fdc_oneOf "Prepend the provided list of values to the existing list." - prepend: [Any!] + prepend: [Any!] @fdc_oneOf + "Append values that do not already exist to the list." + add: [Any!] @fdc_oneOf + "Remove all occurrences of each value from the list." + remove: [Any!] @fdc_oneOf } type Query { @@ -1807,7 +2088,7 @@ Defaults to `INNER_PRODUCT`. View [all vector functions](https://github.com/pgvector/pgvector?tab=readme-ov-file#vector-functions). """ -enum VectorSimilarityMethod { +enum VectorSimilarityMethod @fdc_forbiddenAsFieldType { "Measures the Euclidean (L2) distance between two vectors." L2 "Measures the cosine similarity between two vectors." @@ -1841,31 +2122,6 @@ input Vector_ListFilter { excludesAll: [Vector!] } -"Update input of a Vector value." -input Vector_Update { - "Set the field to the provided vector value." - set: Vector @fdc_oneOf(group: "set") - "Set the field to the vector embedding result from a text input." - set_embed: Vector_Embed @fdc_oneOf(group: "set") -} - - -"Update input of a Vector list value." -input Vector_ListUpdate { - "Replace the current list with the provided list of Vector values." - set: [Vector] - "Append the provided Vector values to the existing list." - append: [Vector] - "Prepend the provided Vector values to the existing list." - prepend: [Vector] - "Delete the vector at the specified index." - delete: Int - "The index of the vector to be updated." - i: Int - "Update the vector at the specified index." - update: Vector -} - """ Create a vector embedding of text using the given model on Vertex AI. @@ -1934,73 +2190,5 @@ scalar Vector_Embed_Model @fdc_example(value: "textembedding-gecko@001", description: "An older version of the textembedding-gecko model") @fdc_example(value: "text-embedding-004", description: "Another text embedding model") -""" -Redact a part of the response from the client. - -Redacted fields are still evaluated for side effects (including data changes and -`@check`) and the results are still available to later steps in CEL expressions -(via `response.fieldName`). -""" -directive @redact on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT - -""" -Ensure this field is present and is not null or `[]`, or abort the request / transaction. - -A CEL expression, `expr` is used to test the field value. It defaults to -rejecting null and `[]` but a custom expression can be provided instead. - -If the field occurs multiple times (i.e. directly or indirectly nested under a -list), `expr` will be executed once for each occurrence and `@check` succeeds if -all values succeed. `@check` fails when the field is not present at all (i.e. -all ancestor paths contain `null` or `[]`), unless `optional` is true. - -If a `@check` fails in a mutation, the top-level field containing it will be -replaced with a partial error, whose message can be customzied via the `message` -argument. Each subsequent top-level fields will return an aborted error (i.e. -not executed). To rollback previous steps, see `@transaction`. -""" -directive @check( - """ - The CEL expression to test the field value (or values if nested under a list). - - Within the CEL expression, a special value `this` evaluates to the field that - this directive is attached to. If this field occurs multiple times because - any ancestor is a list, each occurrence is tested with `this` bound to each - value. When the field itself is a list or object, `this` follows the same - structure (including all decendants selected in case of objects). - - For any given path, if an ancestor is `null` or `[]`, the field will not be - reached and the CEL evaluation will be skipped for that path. In other words, - evaluation only takes place when `this` is `null` or non-null, but never - undefined. (See also the `optional` argument.) - """ - expr: Boolean_Expr! = "!(this in [null, []])" - """ - The error message to return to the client if the check fails. - - Defaults to "permission denied" if not specified. - """ - message: String! = "permission denied" - """ - Whether the check should pass or fail (default) when the field is not present. - - A field will not be reached at a given path if its parent or any ancestor is - `[]` or `null`. When this happens to all paths, the field will not be present - anywhere in the response tree. In other words, `expr` is evaluated 0 times. - By default, @check will automatically fail in this case. Set this argument to - `true` to make it pass even if no tests are run (a.k.a. "vacuously true"). - """ - optional: Boolean = false -) repeatable on QUERY | MUTATION | FIELD | FRAGMENT_DEFINITION | FRAGMENT_SPREAD | INLINE_FRAGMENT - -type Mutation { - """ - Run a query during the mutation and add fields into the response. - - Example: foo: query { users { id } } will add a field foo: {users: [{id: "..."}, …]} into the response JSON. - - Note: Data fetched this way can be handy for permission checks. See @check. - """ - query: Query -} +# Intentionally left blank.