-
Notifications
You must be signed in to change notification settings - Fork 12
feat(agent-bff): serve the agent schema contract on GET /agent/v1/context #1838
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
318862f
refactor(agent-bff): expose the schema cache in the read-model bundle
Tonours f6be717
feat(agent-bff): serialize the allow-listed agent schema as a context…
Tonours c4f19ec
feat(agent-bff): serve GET /agent/v1/context and declare it in the Op…
Tonours 114b3c1
refactor(agent-bff): read the schema and its read-model as one snapshot
Tonours adbccbb
test(agent-bff): constrain the schema snapshot generation pairing
Tonours 10516a5
fix(agent-bff): tolerate a null enums list from the agent schema
Tonours e68a428
fix(agent-bff): declare the 400 the context route can return
Tonours 654c086
feat(agent-bff): carry the resolved environment id in the context meta
Tonours 817de19
feat(agent-bff): carry field validations in the context contract
Tonours 09f927d
feat(agent-bff): carry enum values and the primary-key flag in the co…
Tonours 2100d99
refactor(agent-bff): tighten the context serializer and drop a redund…
Tonours 1768f10
fix(agent-bff): skip a null action field instead of failing the conte…
Tonours baaa53b
feat(agent-bff): carry relation metadata in the context contract
Tonours 1518a9a
feat(agent-bff): accept a BFF api key on the context route
Tonours 56b0049
fix(agent-bff): correct the context route status codes, security doc …
Tonours 863fe7a
refactor(agent-bff): drop the dead collection filter and pin the cont…
Tonours f0e4a31
test(agent-bff): round-trip the context contract and pin unserved rel…
Tonours f270080
fix(agent-bff): type the context field type and cover the environment id
Tonours 5662fbb
test(agent-bff): pin the dotted reference and drop the test comments
Tonours afa2c9b
fix(agent-bff): stop claiming the context document filters nothing
Tonours c0409d0
fix(agent-bff): type the context field type instead of accepting anyt…
Tonours 985ee75
test(agent-bff): make the schema snapshot pairing test able to fail
Tonours 6240578
test(agent-bff): pin the degraded statuses and the schema ttl refetch
Tonours 876c6f4
refactor(agent-bff): guard every schema list against a non-object entry
Tonours 462a500
refactor(agent-bff): inline the context middleware builder and drop a…
Tonours 55bada1
fix(agent-bff): carry the enums of a composite sub-field in the contract
Tonours a924e22
refactor(agent-bff): drop the guards on lists the read-model already …
Tonours 74dac7d
refactor(agent-bff): type the validations input instead of casting it
Tonours 7aa5f7d
test(agent-bff): pin the revision read and the nested array type
Tonours fc57061
fix(agent-bff): skip a non-object schema field and keep an empty enum…
Tonours a0407f7
test(agent-bff): assert the warm-cache response instead of only the f…
Tonours 4369061
test(agent-bff): scope the tag invariants to the collection operations
Tonours 7b7015d
fix(agent-bff): serve only the action the read-model resolved for a name
Tonours File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| import type { FieldType } from '../read-model/capabilities-cache'; | ||
| import type ReadModel from '../read-model/read-model'; | ||
| import type { RelationshipType } from '../read-model/read-model'; | ||
| import type { | ||
| ForestSchemaAction, | ||
| ForestSchemaCollection, | ||
| ForestSchemaField, | ||
| } from '@forestadmin/forestadmin-client'; | ||
|
|
||
| export interface ContextActionField { | ||
| field: string; | ||
| type: FieldType; | ||
| isRequired?: boolean; | ||
| defaultValue?: unknown; | ||
| enums?: string[]; | ||
| } | ||
|
|
||
| export interface ContextAction { | ||
| id: string; | ||
| name: string; | ||
| type: ForestSchemaAction['type']; | ||
| fields: ContextActionField[]; | ||
| } | ||
|
|
||
| export interface ContextValidation { | ||
| type: string; | ||
| value?: unknown; | ||
| } | ||
|
|
||
| export interface ContextField { | ||
| field: string; | ||
| type: FieldType; | ||
| relationship?: RelationshipType; | ||
| reference?: string; | ||
| inverseOf?: string; | ||
| polymorphicTargets?: string[]; | ||
| isPrimaryKey?: boolean; | ||
| isRequired?: boolean; | ||
| isReadOnly?: boolean; | ||
| enums?: string[]; | ||
| validations?: ContextValidation[]; | ||
| } | ||
|
|
||
| export interface ContextCollection { | ||
| name: string; | ||
| fields: ContextField[]; | ||
| actions: ContextAction[]; | ||
| } | ||
|
|
||
| export interface ContextMeta { | ||
| schemaRevision: number; | ||
| environmentId?: number; | ||
| } | ||
|
|
||
| export interface AgentContext { | ||
| collections: ContextCollection[]; | ||
| meta: ContextMeta; | ||
| } | ||
|
|
||
| function toArray<T>(value: T[] | null | undefined): T[] { | ||
| return Array.isArray(value) ? value : []; | ||
| } | ||
|
|
||
| type FieldWithWireEnums = ForestSchemaField & { enums?: string[] }; | ||
|
|
||
| function toContextValidations(validations: unknown[] | null | undefined): ContextValidation[] { | ||
| return toArray(validations) | ||
| .filter( | ||
| (entry): entry is { type: string; value?: unknown } => | ||
| typeof entry === 'object' && | ||
| entry !== null && | ||
| typeof (entry as { type?: unknown }).type === 'string', | ||
| ) | ||
| .map(entry => | ||
| 'value' in entry ? { type: entry.type, value: entry.value } : { type: entry.type }, | ||
| ); | ||
| } | ||
|
|
||
| function toContextField(field: FieldWithWireEnums): ContextField { | ||
| const serialized: ContextField = { field: field.field, type: field.type }; | ||
|
|
||
| if (field.relationship) serialized.relationship = field.relationship; | ||
| if (field.reference) serialized.reference = field.reference; | ||
| if (field.inverseOf) serialized.inverseOf = field.inverseOf; | ||
|
Tonours marked this conversation as resolved.
|
||
|
|
||
| const polymorphicTargets = toArray(field.polymorphicReferencedModels); | ||
| if (polymorphicTargets.length > 0) serialized.polymorphicTargets = [...polymorphicTargets]; | ||
|
Tonours marked this conversation as resolved.
|
||
|
|
||
| if (field.isPrimaryKey) serialized.isPrimaryKey = true; | ||
| if (field.isRequired) serialized.isRequired = true; | ||
| if (field.isReadOnly) serialized.isReadOnly = true; | ||
|
|
||
| if (Array.isArray(field.enums)) serialized.enums = [...field.enums]; | ||
|
|
||
| const validations = toContextValidations(field.validations); | ||
| if (validations.length > 0) serialized.validations = validations; | ||
|
|
||
| return serialized; | ||
| } | ||
|
|
||
| function toContextActionField(field: ForestSchemaAction['fields'][number]): ContextActionField { | ||
| const serialized: ContextActionField = { field: field.field, type: field.type }; | ||
|
|
||
| if (field.isRequired !== undefined) serialized.isRequired = field.isRequired; | ||
| if (field.defaultValue !== undefined) serialized.defaultValue = field.defaultValue; | ||
| if (Array.isArray(field.enums)) serialized.enums = [...field.enums]; | ||
|
|
||
| return serialized; | ||
| } | ||
|
|
||
| function toContextAction(action: ForestSchemaAction): ContextAction { | ||
| return { | ||
| id: action.id, | ||
| name: action.name, | ||
| type: action.type, | ||
| fields: toArray(action.fields) | ||
| .filter(field => typeof field === 'object' && field !== null) | ||
| .map(toContextActionField), | ||
| }; | ||
| } | ||
|
|
||
| function toContextCollection( | ||
| collection: ForestSchemaCollection, | ||
| readModel: ReadModel, | ||
| ): ContextCollection { | ||
| const allowedActions = readModel.getActionEndpoints()[collection.name] ?? {}; | ||
|
|
||
| return { | ||
| name: collection.name, | ||
| fields: toArray(collection.fields) | ||
| .filter(field => typeof field === 'object' && field !== null) | ||
| .map(toContextField), | ||
| actions: toArray(collection.actions) | ||
| .filter(action => { | ||
| const allowed = allowedActions[action?.name]; | ||
|
|
||
| return allowed !== undefined && allowed.id === action.id; | ||
| }) | ||
| .map(toContextAction), | ||
| }; | ||
| } | ||
|
|
||
| function toContextMeta({ schemaRevision, environmentId }: ContextMeta): ContextMeta { | ||
| const meta: ContextMeta = { schemaRevision }; | ||
|
|
||
| if (environmentId !== undefined) meta.environmentId = environmentId; | ||
|
|
||
| return meta; | ||
| } | ||
|
|
||
| export default function buildContext( | ||
| collections: ForestSchemaCollection[], | ||
| readModel: ReadModel, | ||
| meta: ContextMeta, | ||
| ): AgentContext { | ||
| return { | ||
| collections: collections.map(collection => toContextCollection(collection, readModel)), | ||
|
Tonours marked this conversation as resolved.
|
||
| meta: toContextMeta(meta), | ||
| }; | ||
| } | ||
30 changes: 30 additions & 0 deletions
30
packages/agent-bff/src/context/context-routes-middleware.ts
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import type ReadModelStore from '../read-model/read-model-store'; | ||
| import type { Middleware } from 'koa'; | ||
|
|
||
| import buildContext from './build-context'; | ||
| import { resolveSchemaSnapshot } from '../http/agent-route-helpers'; | ||
|
|
||
| const CONTEXT_ROUTE = '/agent/v1/context'; | ||
|
|
||
| export interface ContextRoutesMiddlewareOptions { | ||
| store: ReadModelStore; | ||
| environmentId?: number; | ||
| } | ||
|
|
||
| export default function createContextRoutesMiddleware({ | ||
| store, | ||
| environmentId, | ||
| }: ContextRoutesMiddlewareOptions): Middleware { | ||
| return async function contextRoutesMiddleware(ctx, next) { | ||
| if (ctx.path !== CONTEXT_ROUTE || ctx.method !== 'GET') { | ||
| await next(); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| const { collections, readModel, revision } = await resolveSchemaSnapshot(store); | ||
|
|
||
| ctx.status = 200; | ||
| ctx.body = buildContext(collections, readModel, { schemaRevision: revision, environmentId }); | ||
| }; | ||
| } |
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
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
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.