On this pageOverview
Oxlint Plugin
Foldkit projects use oxlint for linting and @foldkit/oxlint-plugin for rules that understand Foldkit naming and Message conventions.
Create Foldkit app includes .oxlintrc.json, a lint script, oxlint, and @foldkit/oxlint-plugin. A generated project includes this .oxlintrc.json:
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript"],
"jsPlugins": [
{
"name": "foldkit",
"specifier": "@foldkit/oxlint-plugin"
}
],
"categories": {
"correctness": "off"
},
"rules": {
"no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"caughtErrorsIgnorePattern": "^_",
"destructuredArrayIgnorePattern": "^_"
}
],
"typescript/no-explicit-any": "error",
"typescript/consistent-type-assertions": [
"error",
{
"assertionStyle": "never"
}
],
"foldkit/no-noop-message": "error",
"foldkit/got-submodel-message-name": "error",
"foldkit/message-binding-matches-tag": "error",
"foldkit/got-prefix-requires-submodel-payload": "error",
"foldkit/no-empty-object-tagged-call": "error",
"foldkit/prefer-callable-message-constructor": "error",
"foldkit/command-binding-matches-name": "error"
},
"ignorePatterns": [
"dist/",
"node_modules/",
"repos/",
"**/*.d.ts",
"vite.config.ts",
"vitest.config.ts",
"**/*.config.js",
"**/*.config.mjs"
]
}The rules below cover Foldkit-specific cases that oxlint does not know about on its own: callable Message constructors, Submodel wrapper Messages with a message payload, matching m() tags, no-field Message constructors, and Command names.
Rejects catch-all Messages that make update branches and traces less meaningful. Name the event that happened instead.
import { m } from 'foldkit/message'
// ❌ Bad
const NoOp = m('NoOp')
// ✅ Good
const ClickedSave = m('ClickedSave')Requires wrapper Messages around Submodel Messages to use the Got*Message convention.
import { m } from 'foldkit/message'
import * as Child from './child'
// ❌ Bad
const ChildChanged = m('ChildChanged', {
message: Child.Message,
})
// ✅ Good
const GotChildMessage = m('GotChildMessage', {
message: Child.Message,
})Keeps a Message binding and its m() tag identical, so renames do not leave misleading traces behind.
import { m } from 'foldkit/message'
// ❌ Bad
const ClickedSave = m('ClickedSubmit')
// ✅ Good
const ClickedSubmit = m('ClickedSubmit')Reserves the Got* prefix for Submodel wrappers. Any Got-prefixed Message must include a child Message payload named message.
import { Schema as S } from 'effect'
import { m } from 'foldkit/message'
import * as Child from './child'
{
// ❌ Bad: Got is reserved for Submodel wrappers.
const GotWeather = m('GotWeather', {
temperature: S.Number,
})
}
{
// ✅ Good: use a name that does not start with Got for Command results.
const ReceivedWeather = m('ReceivedWeather', {
temperature: S.Number,
})
}
{
// ❌ Bad: Got-prefixed wrappers must carry child Messages.
const GotChildMessage = m('GotChildMessage', {
id: S.String,
})
}
{
// ✅ Good: Got wraps a child Message.
const GotChildMessage = m('GotChildMessage', {
id: S.String,
message: Child.Message,
})
}Catches empty-object calls to no-field Message constructors. A no-field Message should be called with no arguments.
import { m } from 'foldkit/message'
const ClickedSave = m('ClickedSave')
// ❌ Bad
const badMessage = ClickedSave({})
// ✅ Good
const goodMessage = ClickedSave()Prevents constructing Messages by typing or casting object literals. Use the callable Schema constructor instead.
import { Schema as S } from 'effect'
import { m } from 'foldkit/message'
const ClickedSave = m('ClickedSave')
const Message = S.Union([ClickedSave])
type Message = typeof Message.Type
// ❌ Bad
const badMessage: Message = {
_tag: 'ClickedSave',
}
// ✅ Good
const goodMessage = ClickedSave()Keeps a Command binding name in sync with the name passed to Command.define.
import { Effect } from 'effect'
import { Command } from 'foldkit'
import { m } from 'foldkit/message'
const CompletedFetchUser = m('CompletedFetchUser')
// ❌ Bad
const SaveUser = Command.define(
'FetchUser',
CompletedFetchUser,
)(Effect.succeed(CompletedFetchUser()))
// ✅ Good
const FetchUser = Command.define(
'FetchUser',
CompletedFetchUser,
)(Effect.succeed(CompletedFetchUser()))