waaseyaa / graphql
GraphQL endpoint + schema introspection for Waaseyaa — optional/experimental L6 surface; see README for the primary JSON:API framing.
Requires
- php: >=8.5
- waaseyaa/access: ^0.1.0-alpha.300
- waaseyaa/api: ^0.1.0-alpha.300
- waaseyaa/entity: ^0.1.0-alpha.300
- waaseyaa/field: ^0.1.0-alpha.300
- waaseyaa/foundation: ^0.1.0-alpha.300
- waaseyaa/routing: ^0.1.0-alpha.300
- waaseyaa/workflows: ^0.1.0-alpha.300
- webonyx/graphql-php: ^15.31.5
Requires (Dev)
- phpunit/phpunit: ^13.0
- waaseyaa/database-legacy: ^0.1.0-alpha.300
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- dev-main / 0.1.x-dev
- v0.1.0-alpha.300
- v0.1.0-alpha.299
- v0.1.0-alpha.298
- v0.1.0-alpha.297
- v0.1.0-alpha.296
- v0.1.0-alpha.295
- v0.1.0-alpha.294
- v0.1.0-alpha.293
- v0.1.0-alpha.292
- v0.1.0-alpha.291
- v0.1.0-alpha.290
- v0.1.0-alpha.289
- v0.1.0-alpha.288
- v0.1.0-alpha.287
- v0.1.0-alpha.286
- v0.1.0-alpha.285
- v0.1.0-alpha.284
- v0.1.0-alpha.283
- v0.1.0-alpha.282
- v0.1.0-alpha.281
- v0.1.0-alpha.280
- v0.1.0-alpha.279
- v0.1.0-alpha.278
- v0.1.0-alpha.277
- v0.1.0-alpha.276
- v0.1.0-alpha.275
- v0.1.0-alpha.274
- v0.1.0-alpha.273
- v0.1.0-alpha.272
- v0.1.0-alpha.271
- v0.1.0-alpha.270
- v0.1.0-alpha.269
- v0.1.0-alpha.268
- v0.1.0-alpha.267
- v0.1.0-alpha.266
- v0.1.0-alpha.265
- v0.1.0-alpha.264
- v0.1.0-alpha.263
- v0.1.0-alpha.262
- v0.1.0-alpha.261
- v0.1.0-alpha.260
- v0.1.0-alpha.259
- v0.1.0-alpha.258
- v0.1.0-alpha.257
- v0.1.0-alpha.256
- v0.1.0-alpha.255
- v0.1.0-alpha.254
- v0.1.0-alpha.253
- v0.1.0-alpha.252
- v0.1.0-alpha.251
- v0.1.0-alpha.250
- v0.1.0-alpha.249
- v0.1.0-alpha.248
- v0.1.0-alpha.247
- v0.1.0-alpha.246
- v0.1.0-alpha.245
- v0.1.0-alpha.244
- v0.1.0-alpha.243
- v0.1.0-alpha.242
- v0.1.0-alpha.241
- v0.1.0-alpha.240
- v0.1.0-alpha.239
- v0.1.0-alpha.238
- v0.1.0-alpha.237
- v0.1.0-alpha.236
- v0.1.0-alpha.235
- v0.1.0-alpha.234
- v0.1.0-alpha.233
- v0.1.0-alpha.232
- v0.1.0-alpha.231
- v0.1.0-alpha.230
- v0.1.0-alpha.229
- v0.1.0-alpha.228
- v0.1.0-alpha.227
- v0.1.0-alpha.226
- v0.1.0-alpha.225
- v0.1.0-alpha.224
- v0.1.0-alpha.223
- v0.1.0-alpha.222
- v0.1.0-alpha.221
- v0.1.0-alpha.220
- v0.1.0-alpha.219
- v0.1.0-alpha.218
- v0.1.0-alpha.217
- v0.1.0-alpha.216
- v0.1.0-alpha.215
- v0.1.0-alpha.214
- v0.1.0-alpha.213
- v0.1.0-alpha.212
- v0.1.0-alpha.211
- v0.1.0-alpha.210
- v0.1.0-alpha.209
- v0.1.0-alpha.208
- v0.1.0-alpha.207
- v0.1.0-alpha.206
- v0.1.0-alpha.205
- v0.1.0-alpha.204
- v0.1.0-alpha.203
- v0.1.0-alpha.202
- v0.1.0-alpha.201
- v0.1.0-alpha.200
- v0.1.0-alpha.199
- v0.1.0-alpha.198
- v0.1.0-alpha.197
- v0.1.0-alpha.196
- v0.1.0-alpha.195
- v0.1.0-alpha.194
- v0.1.0-alpha.193
- v0.1.0-alpha.192
- v0.1.0-alpha.191
- v0.1.0-alpha.190
- v0.1.0-alpha.189
- v0.1.0-alpha.188
- v0.1.0-alpha.187
- v0.1.0-alpha.186
- v0.1.0-alpha.185
- v0.1.0-alpha.184
- v0.1.0-alpha.183
- v0.1.0-alpha.182
- v0.1.0-alpha.181
- v0.1.0-alpha.180
- v0.1.0-alpha.179
- v0.1.0-alpha.178
- v0.1.0-alpha.177
- v0.1.0-alpha.176
- v0.1.0-alpha.175
- v0.1.0-alpha.174
- v0.1.0-alpha.173
- v0.1.0-alpha.172
- v0.1.0-alpha.171
- v0.1.0-alpha.170
- v0.1.0-alpha.169
- v0.1.0-alpha.168
- v0.1.0-alpha.167
- v0.1.0-alpha.166
- v0.1.0-alpha.165
- v0.1.0-alpha.164
- v0.1.0-alpha.163
- v0.1.0-alpha.162
- v0.1.0-alpha.161
- v0.1.0-alpha.160
- v0.1.0-alpha.159
- v0.1.0-alpha.158
- v0.1.0-alpha.157
- v0.1.0-alpha.156
- v0.1.0-alpha.155
- v0.1.0-alpha.154
- v0.1.0-alpha.153
- v0.1.0-alpha.152
- v0.1.0-alpha.151
- v0.1.0-alpha.150
- v0.1.0-alpha.149
- v0.1.0-alpha.148
- v0.1.0-alpha.147
- v0.1.0-alpha.146
- v0.1.0-alpha.145
- v0.1.0-alpha.144
- v0.1.0-alpha.142
- v0.1.0-alpha.141
- v0.1.0-alpha.140
- v0.1.0-alpha.139
- v0.1.0-alpha.138
- v0.1.0-alpha.137
- v0.1.0-alpha.136
- v0.1.0-alpha.135
- v0.1.0-alpha.134
- v0.1.0-alpha.133
- v0.1.0-alpha.132
- v0.1.0-alpha.131
- v0.1.0-alpha.130
- v0.1.0-alpha.129
- v0.1.0-alpha.128
- v0.1.0-alpha.127
- v0.1.0-alpha.126
- v0.1.0-alpha.125
- v0.1.0-alpha.124
- v0.1.0-alpha.123
- v0.1.0-alpha.122
- v0.1.0-alpha.121
- v0.1.0-alpha.120
- v0.1.0-alpha.119
- v0.1.0-alpha.118
- v0.1.0-alpha.117
- v0.1.0-alpha.116
- v0.1.0-alpha.115
- v0.1.0-alpha.114
- v0.1.0-alpha.113
- v0.1.0-alpha.112
- v0.1.0-alpha.111
- v0.1.0-alpha.110
- v0.1.0-alpha.109
- v0.1.0-alpha.108
- v0.1.0-alpha.107
- v0.1.0-alpha.106
- v0.1.0-alpha.105
- v0.1.0-alpha.104
- v0.1.0-alpha.103
- v0.1.0-alpha.102
- v0.1.0-alpha.101
- v0.1.0-alpha.100
- v0.1.0-alpha.99
- v0.1.0-alpha.98
- v0.1.0-alpha.97
- v0.1.0-alpha.96
- v0.1.0-alpha.95
- v0.1.0-alpha.94
- v0.1.0-alpha.93
- v0.1.0-alpha.92
- v0.1.0-alpha.91
- v0.1.0-alpha.90
- v0.1.0-alpha.89
- v0.1.0-alpha.88
- v0.1.0-alpha.87
- v0.1.0-alpha.86
- v0.1.0-alpha.85
- v0.1.0-alpha.84
- v0.1.0-alpha.83
- v0.1.0-alpha.82
- v0.1.0-alpha.81
- v0.1.0-alpha.80
- v0.1.0-alpha.79
- v0.1.0-alpha.78
- v0.1.0-alpha.77
- v0.1.0-alpha.76
- v0.1.0-alpha.75
- v0.1.0-alpha.74
- v0.1.0-alpha.73
- v0.1.0-alpha.72
- v0.1.0-alpha.71
- v0.1.0-alpha.70
- v0.1.0-alpha.69
- v0.1.0-alpha.68
- v0.1.0-alpha.67
- v0.1.0-alpha.66
- v0.1.0-alpha.65
- v0.1.0-alpha.64
- v0.1.0-alpha.63
- v0.1.0-alpha.62
- v0.1.0-alpha.61
- v0.1.0-alpha.58
- v0.1.0-alpha.57
- v0.1.0-alpha.56
- v0.1.0-alpha.55
- v0.1.0-alpha.54
- v0.1.0-alpha.53
- v0.1.0-alpha.52
- v0.1.0-alpha.51
- v0.1.0-alpha.50
- v0.1.0-alpha.49
- v0.1.0-alpha.48
- v0.1.0-alpha.47
- v0.1.0-alpha.46
- v0.1.0-alpha.45
- v0.1.0-alpha.44
- v0.1.0-alpha.43
- v0.1.0-alpha.42
- v0.1.0-alpha.41
- v0.1.0-alpha.40
- v0.1.0-alpha.39
- v0.1.0-alpha.38
- v0.1.0-alpha.37
- v0.1.0-alpha.36
- v0.1.0-alpha.35
- v0.1.0-alpha.34
- v0.1.0-alpha.33
- v0.1.0-alpha.32
- v0.1.0-alpha.31
- v0.1.0-alpha.30
- v0.1.0-alpha.29
- v0.1.0-alpha.28
- v0.1.0-alpha.27
- v0.1.0-alpha.26
- v0.1.0-alpha.25
- v0.1.0-alpha.24
- v0.1.0-alpha.23
- v0.1.0-alpha.22
- v0.1.0-alpha.21
- v0.1.0-alpha.20
- v0.1.0-alpha.19
- v0.1.0-alpha.18
- v0.1.0-alpha.17
- v0.1.0-alpha.16
- v0.1.0-alpha.15
- v0.1.0-alpha.14
- v0.1.0-alpha.13
- v0.1.0-alpha.12
- v0.1.0-alpha.11
- v0.1.0-alpha.9
- v0.1.0-alpha.8
- v0.1.0-alpha.7
- v0.1.0-alpha.6
- v0.1.0-alpha.5
- v0.1.0-alpha.4
- v0.1.0-alpha.3
- v0.1.0-alpha.2
- v0.1.0-alpha.1
This package is auto-updated.
Last update: 2026-09-02 16:15:43 UTC
README
Alternative protocol — not the primary API surface.
Per the framework's API-surface consolidation (mission
api-surface-consolidation-jsonapi-primary-01KSEFTV), the framework's primary API surface is JSON:API inpackages/api/.waaseyaa/graphqlremains supported as an optional / experimental L6 protocol adapter for distributions whose consumers need GraphQL. It is not bundled bywaaseyaa/full; install it explicitly when your distribution chooses GraphQL.
Layer 6 — Interfaces
GraphQL endpoint for Waaseyaa with auto-generated schema from registered entity types.
GraphQlEndpoint accepts queries at the configured route (registered via GraphQlRouteProvider) and resolves them against EntityTypeManagerInterface-derived schemas. Connection-style pagination follows the Relay spec: totalCount reflects the full unfiltered dataset (matching JSON:API semantics — see #436), while items returns only the access-filtered subset. Field resolvers honour FieldAccessPolicyInterface so attribute-level access control matches the JSON:API surface.
Key classes: GraphQlEndpoint, GraphQlRouteProvider, GraphQlServiceProvider.
Status
- Stability: optional / experimental. The public API surface (
GraphQlServiceProvider, the/graphqlendpoint, the schema-loading mechanism, any documented resolvers / mutations) is frozen at its current shape. The framework cadence ships no new feature work for this package; community contributions are accepted under the same review bar. - Bundle membership: suggested by
waaseyaa/full(not required). To install:composer require waaseyaa/graphql. - Decision provenance: API-surface consolidation by mission
api-surface-consolidation-jsonapi-primary-01KSEFTV. JSON:API is declared the framework's primary API surface indocs/specs/jsonapi.md.
Implementation gotchas
- Reference fields keep storage field names: A field defined as
author_idwith typeentity_referenceproduces a GraphQL field namedauthor_id(notauthor). It resolves to the nested entity object but the field name includes the_idsuffix. - List filter/sort fields are gated through field-level access (R14, audit A11):
EntityResolver::resolveList()applies caller-supplied filter/sort arguments as raw storage conditions. Previouslytotalanditemswere gated only by the entity-levelguard->canView()predicate, so a field restricted per row by a dynamicFieldAccessPolicy(a classification/clearance field) was a presence/ordering oracle: filteringfilter: [{field: "classification_field", value: "secret"}]returned that value's row count even though the caller could not read the field.resolveList()now excludes a row from BOTH the count loop and the item loop when any caller-supplied filter/sort field is view-Forbiddenfor it (GraphQlAccessGuard::isFieldViewForbidden()), value-independently (dropped because the caller may not READ the queried field, never because of its value), matching the RESTJsonApiController::index()fix. BecauseQueryApplierruns sort+pagination in storage before that drop, a sort on a field view-Forbiddenon any viewable matched row is additionally REJECTED (EntityResolver::rejectForbiddenSort()throws aUserError), so a Forbidden row can never occupy an observable pagination rank (the empty-vs-populated-page ordering oracle). Gated to the bound-account path; the system-context bypass keeps the raw storageCOUNT. Structural allowlist (R15, audit A11): the residual the R14 entry flagged is now closed —EntityResolver::assertQueryableFields()runs at the top ofresolveList()(before any storage query, unconditionally) and throws aUserErrorfor any filter/sort field that is not a declared field or entity key, is inALWAYS_INTERNAL_FIELDS(pass/password/password_hash), or is a declared field flaggedsettings['internal'] => true. This mirrors REST'sJsonApiController::validateQueryFields()and closes the undeclared-_data-key oracle (which reachedjson_extract('$.<field>')) and theinternal-flagged-secret oracle (e.g.User.two_factor_secret), both of which R14's per-policy gate could not see. Seedocs/specs/api-layer.md"Field-access gate on filter/sort fields (audit R14)". Pinned byEntityResolverFieldFilterOracleTest(R14) andEntityResolverStructuralFieldAllowlistTest(R15). - Mutations require an authenticated account (R11):
GraphQlEndpoint::handle()rejects any mutation operation (create{Type}/update{Type}/delete{Type}, any alias oroperationName-selected mutation) for an unauthenticated (AccountInterface::isAuthenticated() === false) caller, for every HTTP method, BEFORE building the schema or invoking a resolver: a uniform error message"Authentication required for mutation operations."(no entity id/type ever named) and the mutation never executes. Queries are unaffected. This closes an anonymous existence oracle:update{Type}/delete{Type}distinguished "entity absent" ("Entity not found: {type}/{id}") from "entity exists but access denied", an anonymous or otherwise-unauthorized caller could enumerate entity ids by diffing the two messages even though every per-entityAccessPolicyInterfacewas itself correct. As defense-in-depth for the authenticated-but-unauthorized case (not blocked by the gate above),EntityResolver::resolveUpdate()/resolveDelete()collapse an access-denied outcome, at BOTH the entity level AND the per-fieldeditlevel (theassertFieldEditAccess()loop is inside the collapse), into the SAME "Entity not found" error the absent-entity branch throws, mirroringresolveSingle(), which has always returnednulluniformly for both cases.GraphQlRouterpropagates the endpoint's HTTP status. A custom update/delete resolver registered throughwithMutationOverrides()replaces the generated resolver and MUST preserve the same absent/access-denied collapse, preferably by delegating toEntityResolver. Seedocs/specs/api-layer.mdfor the full writeup.