Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
1e1e0e7
docs(v3): publish canonical DX migration guide (#224)
olliethedev Aug 29, 2026
dbea244
test(v3): validate migration guard markers (#224)
olliethedev Aug 29, 2026
76a2f4a
docs(v3): address canonical DX review findings (#224)
olliethedev Aug 29, 2026
03fcd7e
test(v3): cover contextual legacy hook forms (#224)
olliethedev Aug 29, 2026
70f2fc8
refactor(v3): centralize canonical guard inventory (#224)
olliethedev Aug 29, 2026
248528d
fix(v3): validate referenced plugin configs (#224)
olliethedev Aug 29, 2026
5b4470b
fix(v3): fail closed on opaque factory config (#224)
olliethedev Aug 29, 2026
78a6d56
fix(v3): close canonical guard bypasses (#224)
olliethedev Aug 29, 2026
3911b60
fix(v3): scope canonical config bindings (#224)
olliethedev Aug 29, 2026
127e50b
fix(v3): reject opaque lifecycle hook spreads (#224)
olliethedev Aug 29, 2026
1f8b74a
fix(v3): validate lifecycle hook bindings (#224)
olliethedev Aug 29, 2026
447751a
fix(v3): fail closed on opaque plugin config (#224)
olliethedev Aug 29, 2026
08d3838
fix(v3): close canonical guide gaps (#224)
olliethedev Aug 29, 2026
acfe7cd
fix(v3): harden canonical guard alias parsing (#224)
olliethedev Aug 29, 2026
b72afc7
fix(v3): resolve canonical aliases lexically (#224)
olliethedev Aug 29, 2026
bbe5d4d
fix(v3): bound canonical alias fences (#224)
olliethedev Aug 29, 2026
bc8b2b7
docs(v3): document canonical example exports (#224)
olliethedev Aug 29, 2026
be7a3b8
fix(v3): inspect canonical factory call syntax (#224)
olliethedev Aug 29, 2026
629e1ca
fix(v3): cover namespace factory calls (#224)
olliethedev Aug 29, 2026
faa1619
refactor(v3): index canonical calls by scope (#224)
olliethedev Aug 29, 2026
a456d26
refactor(v3): bound canonical migration guard (#224)
olliethedev Aug 29, 2026
7b53969
fix(v3): scan direct factory calls through comments (#224)
olliethedev Aug 29, 2026
9a4f4c5
fix(v3): tighten bounded canonical matchers (#224)
olliethedev Aug 29, 2026
c9edae7
fix(v3): catch flat aggregate error hooks (#224)
olliethedev Aug 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 30 additions & 26 deletions .agents/skills/btst-backend-plugin-dev/REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,34 @@
## defineBackendPlugin shape (api/plugin.ts)

```typescript
export const myBackendPlugin = defineBackendPlugin({
name: "my-plugin",
dbPlugin: dbSchema,
operations: (adapter) => ({
createItem: defineOperation({
input: CreateItemSchema,
permission: itemPermissions.item.create,
facts: () => undefined,
execute: ({ input }) => createItem(adapter, input),
/** Configuration accepted by `myBackendPlugin`. */
export interface MyBackendPluginOptions {
/** Lifecycle callbacks composed around plugin operations. */
hooks?: MyBackendHooks
}

export const myBackendPlugin = (options: MyBackendPluginOptions = {}) =>
defineBackendPlugin({
id: "myPlugin",
dbPlugin: dbSchema,
operations: (adapter) => createMyOperations(adapter, options.hooks),
raw: (adapter) => ({
prefetchForRoute: createItemPrefetchForRoute(adapter),
}),
}),
raw: (adapter) => ({
prefetchForRoute: createItemPrefetchForRoute(adapter),
}),
routes: (_adapter, _context, operations) => ({
createItem: createEndpoint(
"/items",
{ method: "POST", body: CreateItemSchema, requireRequest: true },
operations.createItem.route((ctx) => ctx.body),
),
}),
})
routes: (_adapter, _context, operations) => {
const createItem = createEndpoint(
"/items",
{ method: "POST", body: CreateItemSchema, requireRequest: true },
operations.createItem.route((ctx) => ctx.body),
)
return { createItem } as const
},
})

export type MyApiRouter = ReturnType<typeof myBackendPlugin.routes>
/** Inferred router contract imported by the client plugin. */
export type MyApiRouter = ReturnType<
ReturnType<typeof myBackendPlugin>["routes"]
>
```

## getters.ts
Expand Down Expand Up @@ -114,16 +118,16 @@ export { serializeItem } from "./serializers"

Invoke domain hooks from the operation lifecycle after authorization. Hooks can enforce domain invariants, publish side effects, and observe errors; they are not the authorization policy.

## Plugin stack() wiring (in stack.ts)
## Plugin `createBackendStack()` wiring (in stack.ts)

```typescript
import { stack } from "@btst/stack"
import { createBackendStack } from "@btst/stack/api"
import { myBackendPlugin } from "./src/plugins/my-plugin/api/plugin"

export const myStack = stack({
export const myStack = createBackendStack({
basePath: "/api/data",
plugins: {
myPlugin: myBackendPlugin,
myPlugin: myBackendPlugin(),
},
adapter: (db) => createDrizzleAdapter(schema, db, {}),
})
Expand Down
8 changes: 5 additions & 3 deletions .agents/skills/btst-backend-plugin-dev/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ src/plugins/{name}/
- **`api/index.ts`** — re-export everything from getters + mutations for direct server-side import.
- **`operations.ts`** — define the one maintained business inventory with input validation, exact permission descriptors, authoritative facts, domain execution, and lifecycle hooks.
- Bind HTTP routes to same-key operations. Use `operationRouteMap` only for real route-name mismatches.
- The optional `raw` factory is narrow: first-party plugins expose only `prefetchForRoute` for SSG. Do not duplicate business getters or mutations on `stack().raw`.
- The optional `raw` factory is narrow: first-party plugins expose only `prefetchForRoute` for SSG. Do not duplicate business getters or mutations on `createBackendStack().raw`.
- Use `myStack.forRequest(request).operations.*` for request work and `myStack.trusted.*` for explicitly trusted jobs.

## Key patterns
Expand Down Expand Up @@ -61,7 +61,9 @@ already support.
`myStack` is a module-level const. The `execute` closure runs lazily (only on HTTP request), so `myStack` is always initialised by then:

```typescript
export const myStack = stack({ ... })
import { createBackendStack } from "@btst/stack/api"

export const myStack = createBackendStack({ ... })

const myTool = tool({
execute: async (params) => {
Expand All @@ -80,7 +82,7 @@ const myTool = tool({
- **Wrong adapter type** — use `import type { DBAdapter as Adapter } from "@btst/db"` in getters/mutations/plugin files.
- **`"GET /path"` string keys** — routes use `createEndpoint()`, not string-keyed method/path objects.
- **`ctx.json()`** — does not exist; return data directly from route handlers.
- **Business methods on `stack().raw`** — do not add them. Keep the composed lifecycle explicit through `forRequest(request).operations` or `trusted`, and reserve `raw` for narrow lower-level/SSG helpers.
- **Business methods on `createBackendStack().raw`** — do not add them. Keep the composed lifecycle explicit through `forRequest(request).operations` or `trusted`, and reserve `raw` for narrow lower-level/SSG helpers.
- **Authorization in lifecycle hooks** — routine access control belongs in operation descriptors and the one shared rule. Hooks receive already-authorized context.
- **Write ops in `getters.ts`** — write functions belong in `mutations.ts`, not `getters.ts`.

Expand Down
19 changes: 6 additions & 13 deletions .agents/skills/btst-build-config/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,21 +117,14 @@ built-in plugin override. Configure `api`, `site`, and `queryClient` once in
accept only plugin-specific options; their loaders and metadata receive shared
runtime through the stack resolver.

### Override type registration (in each layout)

```typescript
import type { YourPluginOverrides } from "@btst/stack/plugins/{name}/client"

type LegacyPluginOverrides = {
"{name}": YourPluginOverrides,
}
```
### Override type inference

Resolved definitions contribute their public override type automatically under
their canonical programmatic ID (`blog`, `aiChat`). Keep a manual map only for
an unmigrated compatibility plugin, and delete it when that plugin adopts the
resolved contract. Do not recreate removed v2 framework, API, guard, or identity
fields in local intersection types.
their canonical programmatic ID (`blog`, `aiChat`). Register the definition in
`createClientStack({ plugins })` and pass that resolved stack to
`StackProvider`; never recreate a manual application override map or provider
generic. Do not restore removed framework, API, guard, or identity fields in
local intersection types.

## Adding shared UI components (@workspace/ui)

Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/btst-client-plugin-dev/REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export function createMyQueryKeys(client: ResourceClient, headers?: HeadersInit)
## Programmatic id (client/constants.ts)

```typescript
export const MY_PLUGIN_ID = "my-plugin" as const
export const MY_PLUGIN_ID = "myPlugin" as const
```

## defineClientPlugin shape (client/plugin.tsx)
Expand Down
Loading
Loading