feat: API-bound function invocation — REST /fn routes + GraphQL bindings plugin (consolidates #1364 + #1365)#1369
Open
pyramation wants to merge 7 commits into
Open
feat: API-bound function invocation — REST /fn routes + GraphQL bindings plugin (consolidates #1364 + #1365)#1369pyramation wants to merge 7 commits into
pyramation wants to merge 7 commits into
Conversation
- Set jwt.claims.api_id transaction-locally via pgSettings for every request on an API surface, derived from the server-side hostname -> services_public.domains -> api_id resolution (never client input) - Add compute module loader (function/invocation module metadata) - Add POST /fn/:alias and GET /fn/invocations/:id REST routes resolving function_api_bindings by (api_id, alias); 404 on missing binding, rest-disabled config, or disallowed method; RLS enforced end to end - ajv/JSON-Schema payload validation intentionally deferred (TODO hook) Refs constructive-io/constructive-planning#1149
…QL mutations One mutation per graphql-enabled function_api_bindings row for the API, with input types derived from JSON Schema / payload_args (JSON fallback) and a plain RLS-enforced insert into function_invocations. Track B2 of constructive-io/constructive-planning#1149
…kt/pg utils and shared fixtures
Contributor
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
- compute loader returns all function modules for the database (no LIMIT 1) - REST /fn/:alias searches every module's bindings table; 409 on cross-scope alias collisions; invocation GET searches each distinct invocations table - graphile plugin takes a modules array and emits mutations from every scope's bindings, inserting into that module's invocations table - loader treats undefined_table (42P01) as module-not-provisioned; all other loader errors propagate - mark the mutation insert step as a side effect for grafast
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Consolidates #1364 (express-context
jwt.claims.api_idprovenance + REST/fn/:aliasroutes) and #1365 (graphile plugin exposing API-bound functions as GraphQL mutations) into one branch, then de-duplicates the overlap and makes the whole path scope-aware. Supersedes both PRs.Both protocols share one binding model end to end: hostname →
services_public.domains→api_id→ trustedjwt.claims.api_idclaim → samefunction_api_bindingsrow (protocol enablement inconfig:{ "graphql": true, "rest": { "path", "methods" } }) → plain RLS-enforced INSERT into that scope'sfunction_invocations. No SECURITY DEFINER, no new tables; ajv payload validation intentionally deferred.Scope-aware module resolution
An API is a global surface; a database may have function modules in multiple scopes, and bindings can live in any of them. The express-context
computeloader now returns all function modules for the database (frommetaschema_modules_public.function_module+function_invocation_module— noLIMIT 1, no pg_catalog discovery, no hard-coded names):POST /fn/:aliassearches every module's bindings table for(api_id, alias); exactly one match proceeds, zero → 404, cross-scope collision → 409. The INSERT targets the matched module's own invocations table.GET /fn/invocations/:idsearches each distinct invocations table.modules: ComputeModuleNames[]array, loads bindings from every scope, and emits one mutation per graphql-enabled binding, inserting into that binding's module invocations table. It emits only mutations — no API→bindings reverse relation is added to the schema.42P01on a module's tables is treated as "module not provisioned" (loader returnsundefined, routes 404); all other loader errors propagate.Cleanup on top of the raw merge
inflekt(toCamelCase/toPascalCase/toConstantCase) for mutation/type/enum naming;escapeIdentifierfrompgreplaces hand-rolled quoting in the plugin andgraphql/server/src/middleware/fn.ts.fn_testSQL; plugin tests seed the shared__fixtures__/seed/{services,compute}fixtures (extended for payload_args derivation, JSON-Schema enum/required, JSON fallback, graphql-disabled, other-API filtering, REST enabled/disabled), so REST and GraphQL tests run against identical data including thefunction_invocations_api_insertWITH CHECK provenance policy.Testing: targeted builds green; plugin
derive+ schema tests,express-context(4/4), andgraphql/server-test/__tests__/fn-routes.integration.test.ts(7/7) pass locally. One plugin test (resize.invocationsub-select) hits a pre-existing local-env pgSelect failure (reading 'items') that also breaks untouchedgraphile-testsuites locally — relying on CI (which runs the plugin suite in the pg-tests matrix) for that path.Link to Devin session: https://app.devin.ai/sessions/3ca7b78eeff8401d9108bb174d8eb3eb
Requested by: @pyramation