From opensaas-migration
Migrate Keystone virtual fields to OpenSaaS Stack format. Invoke as a forked subagent when virtual fields are detected, passing the config file path and field details as arguments.
npx claudepluginhub opensaasau/stack --plugin opensaas-migrationThis skill uses the workspace's default tool permissions.
Migrate the virtual fields described below to OpenSaaS Stack format. OpenSaaS Stack has no GraphQL — virtual fields use `hooks.resolveOutput` with a `type` declaration instead of `graphql.field()`.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Generates original PNG/PDF visual art via design philosophy manifestos for posters, graphics, and static designs on user request.
Migrate the virtual fields described below to OpenSaaS Stack format. OpenSaaS Stack has no GraphQL — virtual fields use hooks.resolveOutput with a type declaration instead of graphql.field().
$ARGUMENTS
For every virtual field, apply this transformation:
Before (Keystone):
import { graphql } from '@keystone-6/core'
fieldName: virtual({
field: graphql.field({
type: graphql.String, // or graphql.Int, graphql.Boolean, etc.
resolve: (item, args, context) => someValue,
}),
})
After (OpenSaaS Stack):
fieldName: virtual({
type: 'string', // see type mapping below
hooks: {
resolveOutput: ({ item, context }) => someValue,
},
})
Keystone graphql.* | OpenSaaS type |
|---|---|
graphql.String | 'string' |
graphql.Int / graphql.Float | 'number' |
graphql.Boolean | 'boolean' |
graphql.list(graphql.String) | 'string[]' |
| Custom object type | { value: MyClass, from: 'package-name' } |
For custom/imported types (e.g. Decimal):
import Decimal from 'decimal.js'
totalPrice: virtual({
type: { value: Decimal, from: 'decimal.js' },
hooks: {
resolveOutput: ({ item }) => new Decimal(item.price).times(item.quantity),
},
})
If the original resolve used context.query.* or context.db.*, replace with context.db.*:
// Before
resolve: async (item, _, context) => {
return context.query.Post.count({ where: { author: { id: { equals: item.id } } } })
}
// After
resolveOutput: async ({ item, context }) => {
return context.db.post.count({ where: { authorId: { equals: item.id } } })
}
virtual() field definitionsgraphql.* type → map to the OpenSaaS type value
b. Extract the resolve function body
c. Rewrite as hooks: { resolveOutput: ({ item, context }) => ... }
d. Replace any context.query.* calls with context.db.* equivalents
e. Note: field arguments (args) are NOT supported — if the original used args, bake in a default or split into multiple fieldsgraphql imports from @keystone-6/core that are no longer used