mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Async query resolution
This commit is contained in:
@@ -1,12 +1,11 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\GraphQL\Builder;
|
||||
use GraphQL\GraphQL;
|
||||
use GraphQL\Type;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Utils\SchemaExtender;
|
||||
use Appwrite\GraphQL\GraphQLPromiseAdapter;
|
||||
use Appwrite\Utopia\Response;
|
||||
use GraphQL\Error\DebugFlag;
|
||||
use GraphQL\Executor\ExecutionResult;
|
||||
use GraphQL\GraphQL;
|
||||
use GraphQL\Type;
|
||||
use Utopia\App;
|
||||
|
||||
App::post('/v1/graphql')
|
||||
@@ -17,67 +16,37 @@ App::post('/v1/graphql')
|
||||
->inject('schema')
|
||||
->inject('utopia')
|
||||
->inject('register')
|
||||
->middleware(true)
|
||||
->action(function ($request, $response, $schema, $utopia, $register) {
|
||||
->inject('dbForProject')
|
||||
->inject('promiseAdapter')
|
||||
->middleware(true)
|
||||
->action(function ($request, $response, $schema, $utopia, $register, $dbForProject, $promiseAdapter) {
|
||||
/** @var Utopia\Swoole\Request $request */
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Type\Schema $schema */
|
||||
/** @var Utopia\App $utopia */
|
||||
/** @var Utopia\Registry\Registry $register */
|
||||
|
||||
$queryType = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'description' => 'The root of all your queries',
|
||||
'fields' => [
|
||||
'accountGet' => [
|
||||
'type' => Type\Definition\Type::string(),
|
||||
'description' => 'Extension description',
|
||||
'args' => [],
|
||||
'resolve' => fn() => "Replacing account get response"
|
||||
],
|
||||
'testQuery' => [
|
||||
'type' => Type\Definition\Type::string(),
|
||||
'description' => 'Extension description 2',
|
||||
'args' => [],
|
||||
'resolve' => fn() => "Test query response"
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
$extendedSchema = SchemaExtender::extend($schema, $queryType->astNode);
|
||||
/** @var \Utopia\Database\Database $dbForProject */
|
||||
|
||||
$query = $request->getPayload('query', '');
|
||||
$variables = $request->getPayload('variables', null);
|
||||
$variables = $request->getPayload('variables');
|
||||
|
||||
$response->setContentType(Response::CONTENT_TYPE_NULL);
|
||||
$register->set('__app', function() use ($utopia) {
|
||||
return $utopia;
|
||||
});
|
||||
$register->set('__response', function() use ($response) {
|
||||
return $response;
|
||||
});
|
||||
|
||||
$isDevelopment = App::isDevelopment();
|
||||
$version = App::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
try {
|
||||
$debug = $isDevelopment ? ( DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE ) : DebugFlag::NONE;
|
||||
$rootValue = [];
|
||||
$result = GraphQL::executeQuery($extendedSchema, $query, $rootValue, null, $variables)
|
||||
->setErrorFormatter(Builder::getErrorFormatter($isDevelopment, $version));
|
||||
$output = $result->toArray($debug);
|
||||
} catch (\Exception $error) {
|
||||
$output = [
|
||||
'errors' => [
|
||||
[
|
||||
'message' => $error->getMessage().'xxx',
|
||||
'code' => $error->getCode(),
|
||||
'file' => $error->getFile(),
|
||||
'line' => $error->getLine(),
|
||||
'trace' => $error->getTrace(),
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
$response->json($output);
|
||||
}
|
||||
);
|
||||
$debugFlags = $isDevelopment
|
||||
? DebugFlag::INCLUDE_DEBUG_MESSAGE | DebugFlag::INCLUDE_TRACE
|
||||
: DebugFlag::NONE;
|
||||
$rootValue = [];
|
||||
|
||||
GraphQL::promiseToExecute(
|
||||
$promiseAdapter,
|
||||
$schema,
|
||||
$query,
|
||||
$rootValue,
|
||||
null,
|
||||
$variables
|
||||
)->then(function (ExecutionResult $result) use ($response, $debugFlags) {
|
||||
$response->json($result->toArray($debugFlags));
|
||||
});
|
||||
});
|
||||
|
||||
+8
-9
@@ -23,6 +23,7 @@ use Ahc\Jwt\JWTException;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\GraphQL\Builder;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\IP;
|
||||
use Appwrite\Network\Validator\URL;
|
||||
@@ -855,22 +856,20 @@ App::setResource('geodb', function($register) {
|
||||
return $register->get('geodb');
|
||||
}, ['register']);
|
||||
|
||||
App::setResource('schema', function($utopia, $response, $request, $register) {
|
||||
$schema = null;
|
||||
App::setResource('schema', function($utopia, $response, $request, $register, $dbForProject) {
|
||||
try {
|
||||
/*
|
||||
* Try to get the schema from the register.
|
||||
* If there is no schema catch the exception and generate it.
|
||||
*/
|
||||
// Try to get the schema from the register.
|
||||
// If there is no base schema catch the exception and generate it.
|
||||
// If the base schema exists, extend it with the current project schema.
|
||||
Console::log('Getting Schema from register...');
|
||||
$schema = $register->get('_schema');
|
||||
$schema = Builder::appendSchema($schema, $dbForProject);
|
||||
} catch (Exception $e) {
|
||||
Console::error('Schema not present. Generating Schema...');
|
||||
$schema = Builder::buildModelSchema($utopia, $response, $register);
|
||||
$schema = Builder::buildSchema($utopia, $response, $register, $dbForProject);
|
||||
$register->set('_schema', function () use ($schema){
|
||||
return $schema;
|
||||
});
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}, ['utopia', 'response', 'request', 'register']);
|
||||
}, ['utopia', 'response', 'request', 'register', 'dbForProject']);
|
||||
|
||||
Generated
+48
-45
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "3ef20ad6919a1844f207e06719ad8929",
|
||||
"content-hash": "de0b7c4c493217f3e9575af0e09277f2",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
@@ -300,20 +300,23 @@
|
||||
},
|
||||
{
|
||||
"name": "colinmollenhour/credis",
|
||||
"version": "v1.12.1",
|
||||
"version": "v1.12.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/colinmollenhour/credis.git",
|
||||
"reference": "c27faa11724229986335c23f4b6d0f1d8d6547fb"
|
||||
"reference": "77e6ede2e01c4cfaade114fe1e07d2f9756949f1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/colinmollenhour/credis/zipball/c27faa11724229986335c23f4b6d0f1d8d6547fb",
|
||||
"reference": "c27faa11724229986335c23f4b6d0f1d8d6547fb",
|
||||
"url": "https://api.github.com/repos/colinmollenhour/credis/zipball/77e6ede2e01c4cfaade114fe1e07d2f9756949f1",
|
||||
"reference": "77e6ede2e01c4cfaade114fe1e07d2f9756949f1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.4.0"
|
||||
"php": ">=5.6.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-redis": "Improved performance for communicating with redis"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -338,9 +341,9 @@
|
||||
"homepage": "https://github.com/colinmollenhour/credis",
|
||||
"support": {
|
||||
"issues": "https://github.com/colinmollenhour/credis/issues",
|
||||
"source": "https://github.com/colinmollenhour/credis/tree/v1.12.1"
|
||||
"source": "https://github.com/colinmollenhour/credis/tree/v1.12.2"
|
||||
},
|
||||
"time": "2020-11-06T16:09:14+00:00"
|
||||
"time": "2022-03-08T18:12:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/package-versions-deprecated",
|
||||
@@ -3258,16 +3261,16 @@
|
||||
},
|
||||
{
|
||||
"name": "composer/semver",
|
||||
"version": "3.3.1",
|
||||
"version": "3.3.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/composer/semver.git",
|
||||
"reference": "5d8e574bb0e69188786b8ef77d43341222a41a71"
|
||||
"reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/composer/semver/zipball/5d8e574bb0e69188786b8ef77d43341222a41a71",
|
||||
"reference": "5d8e574bb0e69188786b8ef77d43341222a41a71",
|
||||
"url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9",
|
||||
"reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3319,7 +3322,7 @@
|
||||
"support": {
|
||||
"irc": "irc://irc.freenode.org/composer",
|
||||
"issues": "https://github.com/composer/semver/issues",
|
||||
"source": "https://github.com/composer/semver/tree/3.3.1"
|
||||
"source": "https://github.com/composer/semver/tree/3.3.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -3335,7 +3338,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-03-16T11:22:07+00:00"
|
||||
"time": "2022-04-01T19:23:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/xdebug-handler",
|
||||
@@ -3557,16 +3560,16 @@
|
||||
},
|
||||
{
|
||||
"name": "felixfbecker/language-server-protocol",
|
||||
"version": "1.5.1",
|
||||
"version": "v1.5.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/felixfbecker/php-language-server-protocol.git",
|
||||
"reference": "9d846d1f5cf101deee7a61c8ba7caa0a975cd730"
|
||||
"reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/9d846d1f5cf101deee7a61c8ba7caa0a975cd730",
|
||||
"reference": "9d846d1f5cf101deee7a61c8ba7caa0a975cd730",
|
||||
"url": "https://api.github.com/repos/felixfbecker/php-language-server-protocol/zipball/6e82196ffd7c62f7794d778ca52b69feec9f2842",
|
||||
"reference": "6e82196ffd7c62f7794d778ca52b69feec9f2842",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3607,9 +3610,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/felixfbecker/php-language-server-protocol/issues",
|
||||
"source": "https://github.com/felixfbecker/php-language-server-protocol/tree/1.5.1"
|
||||
"source": "https://github.com/felixfbecker/php-language-server-protocol/tree/v1.5.2"
|
||||
},
|
||||
"time": "2021-02-22T14:02:09+00:00"
|
||||
"time": "2022-03-02T22:36:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "matthiasmullie/minify",
|
||||
@@ -4184,16 +4187,16 @@
|
||||
},
|
||||
{
|
||||
"name": "phpdocumentor/type-resolver",
|
||||
"version": "1.6.0",
|
||||
"version": "1.6.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpDocumentor/TypeResolver.git",
|
||||
"reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706"
|
||||
"reference": "77a32518733312af16a44300404e945338981de3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/93ebd0014cab80c4ea9f5e297ea48672f1b87706",
|
||||
"reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706",
|
||||
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3",
|
||||
"reference": "77a32518733312af16a44300404e945338981de3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4228,9 +4231,9 @@
|
||||
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
|
||||
"support": {
|
||||
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
|
||||
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.0"
|
||||
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1"
|
||||
},
|
||||
"time": "2022-01-04T19:58:01+00:00"
|
||||
"time": "2022-03-15T21:29:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpspec/prophecy",
|
||||
@@ -5139,16 +5142,16 @@
|
||||
},
|
||||
{
|
||||
"name": "sebastian/environment",
|
||||
"version": "5.1.3",
|
||||
"version": "5.1.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/environment.git",
|
||||
"reference": "388b6ced16caa751030f6a69e588299fa09200ac"
|
||||
"reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac",
|
||||
"reference": "388b6ced16caa751030f6a69e588299fa09200ac",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7",
|
||||
"reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5190,7 +5193,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/environment/issues",
|
||||
"source": "https://github.com/sebastianbergmann/environment/tree/5.1.3"
|
||||
"source": "https://github.com/sebastianbergmann/environment/tree/5.1.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -5198,7 +5201,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2020-09-28T05:52:38+00:00"
|
||||
"time": "2022-04-03T09:37:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/exporter",
|
||||
@@ -5781,16 +5784,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v6.0.5",
|
||||
"version": "v6.0.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "3bebf4108b9e07492a2a4057d207aa5a77d146b1"
|
||||
"reference": "70dcf7b2ca2ea08ad6ebcc475f104a024fb5632e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/3bebf4108b9e07492a2a4057d207aa5a77d146b1",
|
||||
"reference": "3bebf4108b9e07492a2a4057d207aa5a77d146b1",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/70dcf7b2ca2ea08ad6ebcc475f104a024fb5632e",
|
||||
"reference": "70dcf7b2ca2ea08ad6ebcc475f104a024fb5632e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5856,7 +5859,7 @@
|
||||
"terminal"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/v6.0.5"
|
||||
"source": "https://github.com/symfony/console/tree/v6.0.7"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -5872,7 +5875,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-02-25T10:48:52+00:00"
|
||||
"time": "2022-03-31T17:18:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-grapheme",
|
||||
@@ -6390,16 +6393,16 @@
|
||||
},
|
||||
{
|
||||
"name": "twig/twig",
|
||||
"version": "v3.3.8",
|
||||
"version": "v3.3.9",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/twigphp/Twig.git",
|
||||
"reference": "972d8604a92b7054828b539f2febb0211dd5945c"
|
||||
"reference": "6ff9b0e440fa66f97f207e181c41340ddfa5683d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/972d8604a92b7054828b539f2febb0211dd5945c",
|
||||
"reference": "972d8604a92b7054828b539f2febb0211dd5945c",
|
||||
"url": "https://api.github.com/repos/twigphp/Twig/zipball/6ff9b0e440fa66f97f207e181c41340ddfa5683d",
|
||||
"reference": "6ff9b0e440fa66f97f207e181c41340ddfa5683d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -6450,7 +6453,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/twigphp/Twig/issues",
|
||||
"source": "https://github.com/twigphp/Twig/tree/v3.3.8"
|
||||
"source": "https://github.com/twigphp/Twig/tree/v3.3.9"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -6462,7 +6465,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-02-04T06:59:48+00:00"
|
||||
"time": "2022-03-25T09:37:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "vimeo/psalm",
|
||||
@@ -6646,5 +6649,5 @@
|
||||
"platform-overrides": {
|
||||
"php": "8.0"
|
||||
},
|
||||
"plugin-api-version": "2.2.0"
|
||||
"plugin-api-version": "2.3.0"
|
||||
}
|
||||
|
||||
+247
-158
@@ -5,28 +5,25 @@ namespace Appwrite\GraphQL;
|
||||
use Appwrite\GraphQL\Types\JsonType;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Model;
|
||||
use GraphQL\Error\Error;
|
||||
use GraphQL\Error\FormattedError;
|
||||
use GraphQL\Type\Definition\ObjectType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use GraphQL\Type\Schema;
|
||||
use Appwrite\GraphQL\Exception;
|
||||
use GraphQL\Error\Error;
|
||||
use GraphQL\Error\FormattedError;
|
||||
use Utopia\CLI\Console;
|
||||
|
||||
class Builder {
|
||||
class Builder
|
||||
{
|
||||
protected static ?JsonType $jsonParser = null;
|
||||
|
||||
/** @var JsonType $jsonParser */
|
||||
protected static $jsonParser = null;
|
||||
|
||||
/** @var array $typeMapping */
|
||||
protected static $typeMapping = null;
|
||||
protected static array $typeMapping = [];
|
||||
|
||||
/**
|
||||
* Function to initialise the typeMapping array with the base cases of the recursion
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init()
|
||||
* Function to initialise the typeMapping array with the base cases of the recursion
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init()
|
||||
{
|
||||
self::$typeMapping = [
|
||||
Model::TYPE_BOOLEAN => Type::boolean(),
|
||||
@@ -40,11 +37,11 @@ class Builder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to create a singleton for $jsonParser
|
||||
*
|
||||
* @return JsonType
|
||||
*/
|
||||
public static function json()
|
||||
* Function to create a singleton for $jsonParser
|
||||
*
|
||||
* @return JsonType
|
||||
*/
|
||||
public static function json()
|
||||
{
|
||||
if (is_null(self::$jsonParser)) {
|
||||
self::$jsonParser = new JsonType();
|
||||
@@ -53,31 +50,31 @@ class Builder {
|
||||
}
|
||||
|
||||
/**
|
||||
* If the map already contains the type, end the recursion and return.
|
||||
* Iterate through all the rules in the response model. Each rule is of the form
|
||||
* [
|
||||
* [KEY 1] => [
|
||||
* 'type' => A string from Appwrite/Utopia/Response
|
||||
* 'description' => A description of the type
|
||||
* 'default' => A default value for this type
|
||||
* 'example' => An example of this type
|
||||
* 'require' => a boolean representing whether this field is required
|
||||
* 'array' => a boolean representing whether this field is an array
|
||||
* ],
|
||||
* [KEY 2] => [
|
||||
* ],
|
||||
* [KEY 3] => [
|
||||
* ] .....
|
||||
* ]
|
||||
* If there are any field names containing characters other than a-z, A-Z, 0-9, _ ,
|
||||
* we need to remove all those characters. Currently Appwrite's Response model has only the
|
||||
* $ sign which is prohibited by the GraphQL spec. So we're only replacing that. We need to replace this with a regex
|
||||
* based approach.
|
||||
*
|
||||
* @param Model $model
|
||||
* @param Response $response
|
||||
* @return Type
|
||||
*/
|
||||
* If the map already contains the type, end the recursion and return.
|
||||
* Iterate through all the rules in the response model. Each rule is of the form
|
||||
* [
|
||||
* [KEY 1] => [
|
||||
* 'type' => A string from Appwrite/Utopia/Response
|
||||
* 'description' => A description of the type
|
||||
* 'default' => A default value for this type
|
||||
* 'example' => An example of this type
|
||||
* 'require' => a boolean representing whether this field is required
|
||||
* 'array' => a boolean representing whether this field is an array
|
||||
* ],
|
||||
* [KEY 2] => [
|
||||
* ],
|
||||
* [KEY 3] => [
|
||||
* ] .....
|
||||
* ]
|
||||
* If there are any field names containing characters other than a-z, A-Z, 0-9, _ ,
|
||||
* we need to remove all those characters. Currently Appwrite's Response model has only the
|
||||
* $ sign which is prohibited by the GraphQL spec. So we're only replacing that. We need to replace this with a regex
|
||||
* based approach.
|
||||
*
|
||||
* @param Model $model
|
||||
* @param Response $response
|
||||
* @return Type
|
||||
*/
|
||||
static function getTypeMapping(Model $model, Response $response): Type
|
||||
{
|
||||
if (isset(self::$typeMapping[$model->getType()])) {
|
||||
@@ -88,6 +85,7 @@ class Builder {
|
||||
$name = $model->getType();
|
||||
$fields = [];
|
||||
$type = null;
|
||||
|
||||
foreach ($rules as $key => $props) {
|
||||
$keyWithoutSpecialChars = str_replace('$', '_', $key);
|
||||
if (isset(self::$typeMapping[$props['type']])) {
|
||||
@@ -96,7 +94,7 @@ class Builder {
|
||||
try {
|
||||
$complexModel = $response->getModel($props['type']);
|
||||
$type = self::getTypeMapping($complexModel, $response);
|
||||
} catch (Exception $e) {
|
||||
} catch (\Exception $e) {
|
||||
Console::error("Could Not find model for : {$props['type']}");
|
||||
}
|
||||
}
|
||||
@@ -112,82 +110,204 @@ class Builder {
|
||||
];
|
||||
}
|
||||
$objectType = [
|
||||
'name' => $name,
|
||||
'name' => $name,
|
||||
'fields' => $fields
|
||||
];
|
||||
self::$typeMapping[$name] = new ObjectType($objectType);
|
||||
self::$typeMapping[$name] = new ObjectType($objectType);
|
||||
|
||||
return self::$typeMapping[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to map a Utopia\Validator to a valid GraphQL Type
|
||||
*
|
||||
* @param $validator
|
||||
* @param bool $required
|
||||
* @param $utopia
|
||||
* @param $injections
|
||||
* @return GraphQL\Type\Definition\Type
|
||||
*/
|
||||
protected static function getArgType($validator, bool $required, $utopia, $injections): Type
|
||||
/**
|
||||
* Function to map a Utopia\Validator to a valid GraphQL Type
|
||||
*
|
||||
* @param $validator
|
||||
* @param bool $required
|
||||
* @param $utopia
|
||||
* @param $injections
|
||||
* @return GraphQL\Type\Definition\Type
|
||||
*/
|
||||
protected static function getArgType($validator, bool $required, $utopia, $injections): Type
|
||||
{
|
||||
$validator = (\is_callable($validator)) ? call_user_func_array($validator, $utopia->getResources($injections)) : $validator;
|
||||
$type = [];
|
||||
switch ((!empty($validator)) ? \get_class($validator) : '') {
|
||||
case 'Utopia\Validator\Email':
|
||||
case 'Utopia\Validator\Host':
|
||||
case 'Utopia\Validator\Length':
|
||||
case 'Appwrite\Auth\Validator\Password':
|
||||
case 'Utopia\Validator\URL':
|
||||
case 'Appwrite\Database\Validator\UID':
|
||||
case 'Appwrite\Storage\Validator\File':
|
||||
case 'Utopia\Validator\WhiteList':
|
||||
case 'Utopia\Validator\Text':
|
||||
$type = Type::string();
|
||||
break;
|
||||
case 'Utopia\Validator\Boolean':
|
||||
$type = Type::boolean();
|
||||
break;
|
||||
case 'Appwrite\Database\Validator\UID':
|
||||
$type = Type::string();
|
||||
break;
|
||||
case 'Utopia\Validator\Email':
|
||||
$type = Type::string();
|
||||
break;
|
||||
case 'Utopia\Validator\URL':
|
||||
$type = Type::string();
|
||||
break;
|
||||
case 'Utopia\Validator\JSON':
|
||||
case 'Utopia\Validator\Mock':
|
||||
case 'Utopia\Validator\Assoc':
|
||||
$type = self::json();
|
||||
break;
|
||||
case 'Appwrite\Storage\Validator\File':
|
||||
$type = Type::string();
|
||||
case 'Utopia\Validator\ArrayList':
|
||||
$type = Type::listOf(self::json());
|
||||
break;
|
||||
case 'Appwrite\Auth\Validator\Password':
|
||||
$type = Type::string();
|
||||
break;
|
||||
case 'Utopia\Validator\Range': /* @var $validator \Utopia\Validator\Range */
|
||||
$type = Type::int();
|
||||
break;
|
||||
case 'Utopia\Validator\Numeric':
|
||||
case 'Utopia\Validator\Range':
|
||||
$type = Type::int();
|
||||
break;
|
||||
case 'Utopia\Validator\Length':
|
||||
$type = Type::string();
|
||||
break;
|
||||
case 'Utopia\Validator\Host':
|
||||
$type = Type::string();
|
||||
break;
|
||||
case 'Utopia\Validator\WhiteList': /* @var $validator \Utopia\Validator\WhiteList */
|
||||
$type = Type::string();
|
||||
break;
|
||||
case 'Utopia\Validator\Assoc':
|
||||
default:
|
||||
$type = self::json();
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if ($required) {
|
||||
$type = Type::nonNull($type);
|
||||
}
|
||||
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
public static function appendSchema($schema, $dbForProject): Schema
|
||||
{
|
||||
Console::log("[INFO] Appending GraphQL Database Schema...");
|
||||
$start = microtime(true);
|
||||
|
||||
$db = self::buildDatabaseSchema($dbForProject);
|
||||
|
||||
$queryFields = $schema->getQueryType()?->getFields() ?? [];
|
||||
$mutationFields = $schema->getMutationType()?->getFields() ?? [];
|
||||
|
||||
$queryFields = \array_merge($queryFields, $db['query']);
|
||||
$mutationFields = \array_merge($mutationFields, $db['mutation']);
|
||||
|
||||
ksort($queryFields);
|
||||
ksort($mutationFields);
|
||||
|
||||
$schema = new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'description' => 'The root of all queries',
|
||||
'fields' => $queryFields
|
||||
]),
|
||||
'mutation' => new ObjectType([
|
||||
'name' => 'Mutation',
|
||||
'description' => 'The root of all mutations',
|
||||
'fields' => $mutationFields
|
||||
])
|
||||
]);
|
||||
|
||||
$time_elapsed_secs = microtime(true) - $start;
|
||||
Console::log("[INFO] Time Taken To Append Database to API Schema : ${time_elapsed_secs}s");
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
public static function buildSchema($utopia, $response, $register, $dbForProject): Schema
|
||||
{
|
||||
$db = self::buildDatabaseSchema($dbForProject);
|
||||
$api = self::buildAPISchema($utopia, $response, $register, $dbForProject);
|
||||
|
||||
$queryFields = \array_merge($api['query'], $db['query']);
|
||||
$mutationFields = \array_merge($api['mutation'], $db['mutation']);
|
||||
|
||||
ksort($queryFields);
|
||||
ksort($mutationFields);
|
||||
|
||||
return new Schema([
|
||||
'query' => new ObjectType([
|
||||
'name' => 'Query',
|
||||
'description' => 'The root of all your queries',
|
||||
'fields' => $queryFields
|
||||
]),
|
||||
'mutation' => new ObjectType([
|
||||
'name' => 'Mutation',
|
||||
'description' => 'The root of all your mutations',
|
||||
'fields' => $mutationFields
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function goes through all the project attributes and builds a
|
||||
* GraphQL schema for all the collections they make up.
|
||||
*
|
||||
* @param $dbForProject
|
||||
* @return array
|
||||
*/
|
||||
public static function buildDatabaseSchema($dbForProject): array
|
||||
{
|
||||
Console::log("[INFO] Building GraphQL Database Schema...");
|
||||
$start = microtime(true);
|
||||
|
||||
$attrs = $dbForProject->getCollection('attributes');
|
||||
|
||||
$queryFields = [];
|
||||
$mutationFields = [];
|
||||
$collections = [];
|
||||
|
||||
foreach ($attrs as $attr) {
|
||||
$collectionId = $attr->getAttribute('collectionId');
|
||||
|
||||
if (isset(self::$typeMapping[$collectionId])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$key = $attr->getAttribute('key');
|
||||
$type = $attr->getAttribute('type');
|
||||
$keyWithoutSpecialChars = str_replace('$', '_', $key);
|
||||
|
||||
$collections[$collectionId][$keyWithoutSpecialChars] = [
|
||||
'type' => $type,
|
||||
'resolve' => function ($object, $args, $context, $info) use ($key) {
|
||||
return $object->getAttribute($key);
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
$args = [];
|
||||
|
||||
foreach ($collections as $id => $fields) {
|
||||
$objectType = new ObjectType([
|
||||
'name' => $id,
|
||||
'fields' => $fields
|
||||
]);
|
||||
|
||||
self::$typeMapping[$id] = $objectType;
|
||||
|
||||
foreach ($fields as $field => $fieldInfo) {
|
||||
$args[$field] = [
|
||||
'type' => $fieldInfo['type']
|
||||
];
|
||||
}
|
||||
|
||||
$resolve = function ($type, $args, $context, $info) use (&$register, $dbForProject) {
|
||||
return SwoolePromise::create(function (callable $resolve, callable $reject) use ($type, $args, $dbForProject) {
|
||||
try {
|
||||
$resolve($dbForProject->getCollection($type));
|
||||
} catch (\Throwable $e) {
|
||||
$reject($e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
$field = [
|
||||
'type' => $type,
|
||||
'args' => $args,
|
||||
'resolve' => $resolve
|
||||
];
|
||||
|
||||
$queryFields[$id] = $field;
|
||||
$mutationFields[$id] = $field;
|
||||
}
|
||||
|
||||
$time_elapsed_secs = microtime(true) - $start;
|
||||
Console::log("[INFO] Time Taken To Build Database Schema : ${time_elapsed_secs}s");
|
||||
|
||||
return [
|
||||
'query' => $queryFields,
|
||||
'mutation' => $mutationFields
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* This function goes through all the REST endpoints in the API and builds a
|
||||
* GraphQL schema for all those routes whose response model is neither empty nor NONE
|
||||
@@ -195,46 +315,23 @@ class Builder {
|
||||
* @param $utopia
|
||||
* @param $response
|
||||
* @param $register
|
||||
* @return Schema
|
||||
* @param $dbForProject
|
||||
* @return array
|
||||
*/
|
||||
public static function buildDatabaseSchema($utopia, $response, $register)
|
||||
public static function buildAPISchema($utopia, $response, $register, $dbForProject): array
|
||||
{
|
||||
/** @var Model\Collection[] $collections */
|
||||
|
||||
Console::log("[INFO] Building GraphQL Database Schema...");
|
||||
Console::log("[INFO] Building GraphQL API Schema...");
|
||||
$start = microtime(true);
|
||||
$collections = [];
|
||||
|
||||
foreach($collections as $collection) {
|
||||
foreach ($collection->getRules() as $rule) {
|
||||
/** @var Model\Rule $rule */
|
||||
$modelName = $rule->getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function goes through all the REST endpoints in the API and builds a
|
||||
* GraphQL schema for all those routes whose response model is neither empty nor NONE
|
||||
*
|
||||
* @param $utopia
|
||||
* @param $response
|
||||
* @param $register
|
||||
* @return Schema
|
||||
*/
|
||||
public static function buildModelSchema($utopia, $response, $register) {
|
||||
Console::log("[INFO] Building GraphQL Schema...");
|
||||
$start = microtime(true);
|
||||
|
||||
self::init();
|
||||
$queryFields = [];
|
||||
$mutationFields = [];
|
||||
|
||||
foreach($utopia->getRoutes() as $method => $routes ){
|
||||
foreach($routes as $route) {
|
||||
foreach ($utopia->getRoutes() as $method => $routes) {
|
||||
foreach ($routes as $route) {
|
||||
|
||||
$namespace = $route->getLabel('sdk.namespace', '');
|
||||
$methodName = $namespace.'_'.$route->getLabel('sdk.method', '');
|
||||
$methodName = $namespace . '_' . $route->getLabel('sdk.method', '');
|
||||
$responseModelName = $route->getLabel('sdk.response.model', "");
|
||||
|
||||
if ($responseModelName !== "") {
|
||||
@@ -248,28 +345,33 @@ class Builder {
|
||||
$args = [];
|
||||
foreach ($route->getParams() as $key => $value) {
|
||||
$args[$key] = [
|
||||
'type' => self::getArgType($value['validator'],!$value['optional'], $utopia, $value['injections']),
|
||||
'type' => self::getArgType($value['validator'], !$value['optional'], $utopia, $value['injections']),
|
||||
'description' => $value['description'],
|
||||
'defaultValue' => $value['default']
|
||||
];
|
||||
}
|
||||
/* Define a resolve function that defines how to fetch data for this type */
|
||||
$resolve = function ($type, $args, $context, $info) use (&$register, $route) {
|
||||
$utopia = $register->get('__app');
|
||||
$utopia->setRoute($route)->execute($route, $args);
|
||||
$response = $register->get('__response');
|
||||
$result = $response->getPayload();
|
||||
if ( $response->getCurrentModel() == Response::MODEL_ERROR_DEV ) {
|
||||
throw new ExceptionDev($result['message'], $result['code'], $result['version'], $result['file'], $result['line'], $result['trace']);
|
||||
} else if ( $response->getCurrentModel() == Response::MODEL_ERROR ) {
|
||||
throw new Exception($result['message'], $result['code']);
|
||||
}
|
||||
return $result;
|
||||
$resolve = function ($type, $args, $context, $info) use (&$register, $route, $dbForProject) {
|
||||
return SwoolePromise::create(function (callable $resolve, callable $reject) use (&$register, $route, $dbForProject, $args) {
|
||||
$utopia = $register->get('__app');
|
||||
$utopia->setRoute($route)->execute($route, $args);
|
||||
|
||||
$response = $register->get('__response');
|
||||
$result = $response->getPayload();
|
||||
|
||||
if ($response->getCurrentModel() == Response::MODEL_ERROR_DEV) {
|
||||
$reject(new ExceptionDev($result['message'], $result['code'], $result['version'], $result['file'], $result['line'], $result['trace']));
|
||||
} else if ($response->getCurrentModel() == Response::MODEL_ERROR) {
|
||||
$reject(new \Exception($result['message'], $result['code']));
|
||||
}
|
||||
|
||||
$resolve($result);
|
||||
});
|
||||
};
|
||||
|
||||
$field = [
|
||||
'type' => $type,
|
||||
'description' => $description,
|
||||
'description' => $description,
|
||||
'args' => $args,
|
||||
'resolve' => $resolve
|
||||
];
|
||||
@@ -283,41 +385,28 @@ class Builder {
|
||||
}
|
||||
}
|
||||
|
||||
ksort($queryFields);
|
||||
ksort($mutationFields);
|
||||
|
||||
$queryType = new ObjectType([
|
||||
'name' => 'Query',
|
||||
'description' => 'The root of all your queries',
|
||||
'fields' => $queryFields
|
||||
]);
|
||||
$mutationType = new ObjectType([
|
||||
'name' => 'Mutation',
|
||||
'description' => 'The root of all your mutations',
|
||||
'fields' => $mutationFields
|
||||
]);
|
||||
$schema = new Schema([
|
||||
'query' => $queryType,
|
||||
'mutation' => $mutationType
|
||||
]);
|
||||
|
||||
$time_elapsed_secs = microtime(true) - $start;
|
||||
Console::log("[INFO] Time Taken To Build Schema : ${time_elapsed_secs}s");
|
||||
return $schema;
|
||||
Console::log("[INFO] Time Taken To Build API Schema : ${time_elapsed_secs}s");
|
||||
|
||||
return [
|
||||
'query' => $queryFields,
|
||||
'mutation' => $mutationFields
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to create an appropriate GraphQL Error Formatter
|
||||
* Based on whether we're on a development build or production
|
||||
* build of Appwrite.
|
||||
*
|
||||
* build of Appwrite.
|
||||
*
|
||||
* @param bool $isDevelopment
|
||||
* @param string $version
|
||||
* @param string $version
|
||||
* @return callable
|
||||
*/
|
||||
public static function getErrorFormatter(bool $isDevelopment, string $version): callable
|
||||
public
|
||||
static function getErrorFormatter(bool $isDevelopment, string $version): callable
|
||||
{
|
||||
$errorFormatter = function(Error $error) use ($isDevelopment, $version) {
|
||||
$errorFormatter = function (Error $error) use ($isDevelopment, $version) {
|
||||
$formattedError = FormattedError::createFromException($error);
|
||||
/** Previous error represents the actual error thrown by Appwrite server */
|
||||
$previousError = $error->getPrevious() ?? $error;
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\GraphQL;
|
||||
|
||||
use GraphQL\Error\InvariantViolation;
|
||||
use GraphQL\Executor\Promise\Promise;
|
||||
use GraphQL\Executor\Promise\PromiseAdapter;
|
||||
use GraphQL\Utils\Utils;
|
||||
|
||||
class GraphQLPromiseAdapter implements PromiseAdapter
|
||||
{
|
||||
public function isThenable($value): bool
|
||||
{
|
||||
return $value instanceof SwoolePromise;
|
||||
}
|
||||
|
||||
public function convertThenable($thenable): Promise
|
||||
{
|
||||
if (!$thenable instanceof SwoolePromise) {
|
||||
throw new InvariantViolation('Expected instance of SwoolePromise, got ' . Utils::printSafe($thenable));
|
||||
}
|
||||
return new Promise($thenable, $this);
|
||||
}
|
||||
|
||||
public function then(Promise $promise, ?callable $onFulfilled = null, ?callable $onRejected = null): Promise
|
||||
{
|
||||
/** @var SwoolePromise $adoptedPromise */
|
||||
$adoptedPromise = $promise->adoptedPromise;
|
||||
|
||||
return new Promise($adoptedPromise->then($onFulfilled, $onRejected), $this);
|
||||
}
|
||||
|
||||
public function create(callable $resolver): Promise
|
||||
{
|
||||
$promise = new SwoolePromise();
|
||||
try {
|
||||
$resolver(
|
||||
[$promise, 'resolve'],
|
||||
[$promise, 'reject'],
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
$promise->reject($e);
|
||||
}
|
||||
return new Promise($promise, $this);
|
||||
}
|
||||
|
||||
public function createFulfilled($value = null): Promise
|
||||
{
|
||||
$promise = new SwoolePromise();
|
||||
|
||||
return new Promise($promise->resolve($value), $this);
|
||||
}
|
||||
|
||||
public function createRejected($reason): Promise
|
||||
{
|
||||
$promise = new SwoolePromise();
|
||||
|
||||
return new Promise($promise->reject($reason), $this);
|
||||
}
|
||||
|
||||
public function all(array $promisesOrValues): Promise
|
||||
{
|
||||
$all = new SwoolePromise();
|
||||
|
||||
$total = count($promisesOrValues);
|
||||
$count = 0;
|
||||
$result = [];
|
||||
|
||||
foreach ($promisesOrValues as $index => $promiseOrValue) {
|
||||
if ($promiseOrValue instanceof Promise) {
|
||||
$result[$index] = null;
|
||||
$promiseOrValue->then(
|
||||
static function ($value) use ($index, &$count, $total, &$result, $all): void {
|
||||
$result[$index] = $value;
|
||||
$count++;
|
||||
if ($count < $total) {
|
||||
return;
|
||||
}
|
||||
$all->resolve($result);
|
||||
},
|
||||
[$all, 'reject']
|
||||
);
|
||||
} else {
|
||||
$result[$index] = $promiseOrValue;
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
if ($count === $total) {
|
||||
$all->resolve($result);
|
||||
}
|
||||
|
||||
return new Promise($all, $this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
|
||||
namespace Appwrite\GraphQL;
|
||||
|
||||
use Swoole\Coroutine;
|
||||
use Swoole\Coroutine\Channel;
|
||||
|
||||
/**
|
||||
* Class SwoolePromise
|
||||
*
|
||||
* Inspired by https://github.com/streamcommon/promise/blob/master/lib/ExtSwoolePromise.php
|
||||
*
|
||||
* @package Appwrite\GraphQL
|
||||
*/
|
||||
class SwoolePromise
|
||||
{
|
||||
const STATE_PENDING = 1;
|
||||
const STATE_FULFILLED = 0;
|
||||
const STATE_REJECTED = -1;
|
||||
|
||||
protected int $state = self::STATE_PENDING;
|
||||
|
||||
private mixed $result;
|
||||
|
||||
public function __construct(?callable $executor = null)
|
||||
{
|
||||
if (\is_null($executor)) {
|
||||
return;
|
||||
}
|
||||
if (!\extension_loaded('swoole')) {
|
||||
throw new \RuntimeException('Swoole ext missing!');
|
||||
}
|
||||
$resolve = function ($value) {
|
||||
$this->setResult($value);
|
||||
$this->setState(self::STATE_FULFILLED);
|
||||
};
|
||||
$reject = function ($value) {
|
||||
if ($this->isPending()) {
|
||||
$this->setResult($value);
|
||||
$this->setState(self::STATE_REJECTED);
|
||||
}
|
||||
};
|
||||
Coroutine::create(function (callable $executor, callable $resolve, callable $reject) {
|
||||
try {
|
||||
$executor($resolve, $reject);
|
||||
} catch (\Throwable $exception) {
|
||||
$reject($exception);
|
||||
}
|
||||
}, $executor, $resolve, $reject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new promise from the given callable.
|
||||
*
|
||||
* @param callable $promise
|
||||
* @return SwoolePromise
|
||||
*/
|
||||
final public static function create(callable $promise): SwoolePromise
|
||||
{
|
||||
return new static($promise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve promise with given value.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return SwoolePromise
|
||||
*/
|
||||
final public static function resolve(mixed $value): SwoolePromise
|
||||
{
|
||||
return new static(function (callable $resolve) use ($value) {
|
||||
$resolve($value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Rejects the promise with the given reason.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return SwoolePromise
|
||||
*/
|
||||
final public static function reject(mixed $value): SwoolePromise
|
||||
{
|
||||
return new static(function (callable $resolve, callable $reject) use ($value) {
|
||||
$reject($value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Catch any exception thrown by the executor.
|
||||
*
|
||||
* @param callable $onRejected
|
||||
* @return SwoolePromise
|
||||
*/
|
||||
final public function catch(callable $onRejected): SwoolePromise
|
||||
{
|
||||
return $this->then(null, $onRejected);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the promise.
|
||||
*
|
||||
* @param callable|null $onFulfilled
|
||||
* @param callable|null $onRejected
|
||||
* @return SwoolePromise
|
||||
*/
|
||||
public function then(?callable $onFulfilled = null, ?callable $onRejected = null): SwoolePromise
|
||||
{
|
||||
return self::create(function (callable $resolve, callable $reject) use ($onFulfilled, $onRejected) {
|
||||
while ($this->isPending()) {
|
||||
usleep(25000);
|
||||
}
|
||||
$callable = $this->isFulfilled() ? $onFulfilled : $onRejected;
|
||||
if (!is_callable($callable)) {
|
||||
$resolve($this->result);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$resolve($callable($this->result));
|
||||
} catch (\Throwable $error) {
|
||||
$reject($error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a promise that completes when all passed in promises complete.
|
||||
*
|
||||
* @param iterable|SwoolePromise[] $promises
|
||||
* @return SwoolePromise
|
||||
*/
|
||||
public static function all(iterable $promises): SwoolePromise
|
||||
{
|
||||
return self::create(function (callable $resolve, callable $reject) use ($promises) {
|
||||
$ticks = count($promises);
|
||||
|
||||
$firstError = null;
|
||||
$channel = new Channel($ticks);
|
||||
$result = [];
|
||||
$key = 0;
|
||||
|
||||
foreach ($promises as $promise) {
|
||||
if (!$promise instanceof SwoolePromise) {
|
||||
$channel->close();
|
||||
throw new \RuntimeException(
|
||||
'Supported only Appwrite\GraphQL\SwoolePromise instance'
|
||||
);
|
||||
}
|
||||
$promise->then(function ($value) use ($key, $result, $channel) {
|
||||
$result[$key] = $value;
|
||||
$channel->push(true);
|
||||
return $value;
|
||||
}, function ($error) use ($channel, &$firstError) {
|
||||
$channel->push(true);
|
||||
if ($firstError === null) {
|
||||
$firstError = $error;
|
||||
}
|
||||
});
|
||||
$key++;
|
||||
}
|
||||
while ($ticks--) {
|
||||
$channel->pop();
|
||||
}
|
||||
$channel->close();
|
||||
|
||||
if ($firstError !== null) {
|
||||
$reject($firstError);
|
||||
return;
|
||||
}
|
||||
$resolve($result);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set resolved result
|
||||
*
|
||||
* @param mixed $value
|
||||
* @return void
|
||||
*/
|
||||
private function setResult(mixed $value): void
|
||||
{
|
||||
if (!$value instanceof SwoolePromise) {
|
||||
throw new \RuntimeException('Supported only Appwrite\GraphQL\SwoolePromise instance');
|
||||
}
|
||||
$resolved = false;
|
||||
$callable = function ($value) use (&$resolved) {
|
||||
$this->setResult($value);
|
||||
$resolved = true;
|
||||
};
|
||||
$value->then($callable, $callable);
|
||||
|
||||
while (!$resolved) {
|
||||
usleep(25000);
|
||||
}
|
||||
|
||||
$this->result = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change promise state
|
||||
*
|
||||
* @param integer $state
|
||||
* @return void
|
||||
*/
|
||||
final protected function setState(int $state): void
|
||||
{
|
||||
$this->state = $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Promise is pending
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
final protected function isPending(): bool
|
||||
{
|
||||
return $this->state == self::STATE_PENDING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Promise is fulfilled
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
final protected function isFulfilled(): bool
|
||||
{
|
||||
return $this->state == self::STATE_FULFILLED;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user