Skip to content
Back to skills

Owlmeans Code Structure

ASecurity

Mandatory object and layout rules for all OwlMeans TypeScript: the functions of one domain are ONE object built by a factory; its interface is declared FIRST (interfaces, never ReturnType<typeof …>); private parts live inside the factory; the context or collaborator is bound into the factory; types, consts and code live in separate files; an object with its own types or sub-helpers gets a same-named folder. Use whenever you write or change any .ts/.tsx in an @owlmeans package, an app built on...

  • 3 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 6, 2026
developmenttypescriptgoreactexpressgitapifrontendbackend

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned October 6, 2026

npx -y skills add owlmeans/common --skill owlmeans-code-structure --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Owlmeans Code Structure?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Owlmeans Code Structure
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/owlmeans-owlmeans-code-structure/badge)](https://www.skillsdirectory.com/skills/owlmeans-owlmeans-code-structure)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: owlmeans-code-structure
description: "Mandatory object and layout rules for all OwlMeans TypeScript: the functions of one domain are ONE object built by a factory; its interface is declared FIRST (interfaces, never ReturnType<typeof …>); private parts live inside the factory; the context or collaborator is bound into the factory; types, consts and code live in separate files; an object with its own types or sub-helpers gets a same-named folder. Use whenever you write or change any .ts/.tsx in an @owlmeans package, an app built on OwlMeans or a generated target: adding a function, helper, util, service, model, resource, type or constant, creating or splitting a file, or reviewing a diff."
metadata:
  scope: general
---

# OwlMeans code structure

Every OwlMeans repo — the libraries, the platform and every generated application — holds to the
same shape. No tool checks it: this skill is the rule, every diff you write or review is held to it,
and a violation is a defect, not a style note.

## The rules

1. **One domain, one object.** The functions a file offers to other files form ONE object, built by
   ONE factory. Two domains in a file are two files. A file that exports a single function is not a
   bundle and may stay a plain export.
2. **Type first.** Declare the object's `interface` before writing the factory, in the object's
   types file, and make the factory return it: `export const createXxxHelper = (…): XxxHelper => {…}`.
   A type derived from the implementation — `ReturnType<typeof createXxx>`, `Parameters<typeof fn>`,
   `typeof someObject` — never names a helper, util, service, model or resource. The contract's JSDoc
   lives on the interface members.
3. **Encapsulate.** The factory's closure holds the implementation: what the interface does not name
   is a private local. Only pure, stateless primitives may stay module-private above the factory.
   A factory that merely collects module-level functions — `createX = () => ({ a, b })` — is the
   thing this rule forbids. No `this`: members are arrow functions, so they can be destructured and
   passed on.
4. **Bind what is shared.** When every member would take the same first argument — a context, a
   request, a collaborator, a record — that argument goes to the factory, not to each call.
5. **Interfaces over types — always where possible.**
   - `type X = { … }` → `interface X { … }`; `A & B & { … }` → `interface X extends A, B { … }`.
   - `type X = Pick<A, 'a'>` (also `Omit`, `Partial`, `Required`, `Readonly`) → `interface X extends Pick<A, 'a'> {}`.
   - `type Fn = (a: A) => R` → `interface Fn { (a: A): R }`.
   - `type` stays only for what an interface cannot say: unions, mapped, conditional and
     template-literal types, tuples, primitives, `keyof`/indexed access, and protocol trees inferred
     from a DSL (with a one-line reason).
6. **Types, consts and code in separate files.** Interfaces and type aliases → `types.ts`; enums and
   constants (including computed constant expressions such as an `Object.freeze([...LIST, ...])`) →
   `consts.ts`; code everywhere else. Package-private declarations go to `types.local.ts` /
   `consts.local.ts`, which no barrel re-exports.
7. **A same-named folder for an object that has parts.** `helpers/file.ts` holds the factory;
   `helpers/file/` holds its `types.ts`, `consts.ts` and the sub-helpers only it uses. A directory-level
   `types.ts` is right for declarations two or more objects share, or for a small single-owner
   directory; a types file that serves many unrelated owners is split into their folders.
8. **Ready instances.** A zero-argument helper may export one ready instance as the file's last
   statement (`export const pathHelper = createPathHelper()`). A bound object never has one — it is
   reached through its binding target (below). Never build a bound object inside a loop or a render.

## Kinds and names

| Kind | What it is | Factory | Interface | Where |
|---|---|---|---|---|
| util | package-internal functionality, never re-exported by the package | `createXxxUtils()` / `makeXxxUtils(target)` | `XxxUtils` | `utils/xxx.ts` + `utils/xxx/` |
| helper | reusable functionality, may be exported, not bound to one record | `createXxxHelper(opts?)` / `makeXxxHelper(target)` | `XxxHelper` | `xxx.ts` + `xxx/` |
| service | lives in a context, has an alias and a lifecycle | `createXxxService(alias = DEFAULT_ALIAS)` via `createService`, plus `appendXxxService(ctx)` | `XxxService extends InitializedService` | `services/xxx.ts` + `xxx/` |
| model | logic over ONE plain record (plus collaborators) | `makeXxxModel(record, deps?)` | `XxxModel` | `models/xxx.ts` + `xxx/` |
| resource | store access and store-level queries | `makeXxxResource(…)` | `XxxResource` | `resources/xxx.ts` + `xxx/` |

`create*` builds something standalone or configured by options; `make*` builds something bound to
its first argument.

## How a bound object is reached

| Bound to | Shape | At the call site |
|---|---|---|
| the context, with a lifecycle, a cache or a replaceable seam | a service | `ctx.service<XxxService>(ALIAS)`, or an accessor its `append*` installs (`ctx.xxx()`) |
| the context of an app that owns its context type | `makeXxxHelper(ctx)`, installed lazily on the context: `context.projects = memoHelper.once(() => makeProjectHelper(context))` | `ctx.projects().getDevSlot(id)` |
| the context, inside a library | `makeXxx(ctx)` plus `export const xxxOf = memoHelper.oncePer(makeXxx)` | `const xxx = xxxOf(ctx)` once per function |
| one request | `makeRequestScope(ctx, request)` | built once at the top of a handler, never stored |
| a collaborator (a FileHelper, a deps bag) | `makeXxx(collaborator)`, an accessor on the collaborator or `oncePer` | `files.layout().recordDeviation(d)` |
| a record | a model | `makeSlotModel(slot).namespace()` |

`memoHelper` (`once`, `oncePer`) comes from `@owlmeans/context`. Tests build the object directly:
`makeProjectHelper(fakeContext)`.

## Examples

```ts
// helpers/slug/types.ts — the type comes first
export interface SlugHelper {
  /** The URL-safe form of a title. */
  slugOf: (title: string) => string
  /** A title read back from a slug. */
  titleOf: (slug: string) => string
}

// helpers/slug.ts
import type { SlugHelper } from './slug/types.js'

export const createSlugHelper = (): SlugHelper => {
  const words = (text: string): string[] => text.split(/\W+/).filter(word => word !== '') // private

  const slugOf = (title: string): string => words(title.toLowerCase()).join('-')
  const titleOf = (slug: string): string => words(slug).join(' ')

  return { slugOf, titleOf }
}
export const slugHelper = createSlugHelper()
```

A service follows `@owlmeans/api`'s `createApiService(alias): ApiClient` — `createService<ApiClient>`
with the members in place, `assertContext(client.ctx, …)` inside them, and an `appendApiClient(ctx)`
that registers it. A model follows `@owlmeans/planning`'s `makeWorkcardModel(record, facade):
WorkcardModel`.

```ts
// models/invoice/types.ts
export interface InvoiceModel { readonly record: Invoice; isPaid: () => boolean; owed: () => number }
// models/invoice.ts
export const makeInvoiceModel = (record: Invoice): InvoiceModel => ({
  record,
  isPaid: () => record.paidAt != null,
  owed: () => (record.paidAt != null ? 0 : record.amountMinor),
})
```

A model is built from its record when it is needed — never stored on the record, never serialized.

**Wrong, and how it is fixed**

| Wrong | Right |
|---|---|
| `const a = …; const b = …; export const createX = () => ({ a, b })` + `type X = ReturnType<typeof createX>` | `interface X` in `x/types.ts`; `createX = (): X => { const a = …; const b = …; return { a, b } }` |
| `xHelper.read(files, path)`, `xHelper.write(files, …)` — the same collaborator in every call | `makeX(files): X` and `x.read(path)` |
| `export const getDevSlot = (ctx, id) => …` beside five more ctx-first functions | `makeProjectHelper(ctx): ProjectHelper`, reached as `ctx.projects()` |
| `export const SEED_SOURCES = Object.freeze([…])` in a code file | the constant in `consts.ts` |
| one `types.ts` holding the interfaces of twenty unrelated helpers | each helper's interface in its own `<helper>/types.ts` |

## Exempt

These stay plain exports: declaration-builder DSLs that are the framework's vocabulary (`route`,
`protocol`, `typed`, `contract`, `bind`, `logger`, `frontend`/`backend`, `declare*`, config plugins),
factories themselves (`make*`, `create*`, `append*`), React components and `use*` hooks, entry
scripts, error classes and schema files.

## Generated applications

Applications the viable agent generates follow the same rules, in these fixed shapes:

- **Domain model per entity.** `backend/src/models/<entity>/types.ts` declares
  `interface <Base>Model` first; `models/<entity>/<base>.ts` exports only
  `make<Base>Model = (ctx: Context): <Base>Model => { const op = async (actor: Actor, …) => …; return { op, … } }`.
  It is built where it is used — `await makeTaskModel(ctx).complete(actorOf(request), id)` — and is
  never registered as a context service. Its member names are the endpoint keys of the entity.
- **Handlers and job processors**: one plain exported function per file
  (`api/src/app/<entity>/<action>.ts` beside a GENERATED `index.ts` barrel, `worker/src/jobs/<job>.ts`);
  a handler's body is one call into the model.
- **Seed helpers are objects** whose members keep the old function names:
  `actorOf(request): Actor` with `actor.inOrganization(…)`, `actor.assertOrganization(…)`,
  `actor.grantedIds(…)`, `actor.organizationScope(…)`; `recordOwnerOf(request)`, `visitOf(request)`,
  `planningAccessOf(request, ctx)`, `landingHandoff`, `visitKey`. Their types live in the module's
  same-named folder (`lib/actor/types.ts`); the old free functions stay as
  `@deprecated generated-app:factory-objects` wrappers so a project generated before still compiles.
- A model module generated before this shape keeps its plain functions: its callers import them by
  name, so it is extended in that shape and never converted in place.
- **Resources** keep their maker and their `xResource(ctx)` accessor; the resource's interface lives
  in `resources/<entity>/types.ts`.
- Shared types in `common` are interfaces.
- A file the template shipped keeps the shape the platform gives it.

## Self-check before you finish

```sh
{ git diff --name-only HEAD; git ls-files --others --exclude-standard; } | grep -E '\.tsx?$' \
  | grep -vE '\.(test|spec)\.tsx?$|\.d\.ts$|(^|/)(tests?|fixtures)/' > /tmp/changed.txt
# derived helper types and wrapper factories
xargs -r grep -nE 'ReturnType<typeof|= \(\) => \(\{ *[a-zA-Z]+, ' < /tmp/changed.txt
# object-shaped type aliases that should be interfaces
xargs -r grep -nE '^(export )?type [A-Za-z0-9_]+(<[^=]*>)? *= *\{' < /tmp/changed.txt
# types, enums or CONSTANT_CASE constants declared in code files
grep -vE '(^|/)(types|consts)(\.local)?\.ts$' /tmp/changed.txt \
  | xargs -r grep -nE '^export (interface |enum |type [A-Za-z0-9_]+ *(<[^=]*>)? *=|const [A-Z][A-Z0-9_]+ *[:=])'
```

Then read the diff once for what no grep sees: a second exported function of one domain, a member
that takes the same first argument as its siblings, a private helper exposed on the interface, a
bound object built in a loop, a barrel re-exporting a `.local` file.

Classification details, folder decisions and the pitfalls of moving code into closures: `reference.md`
(in the OwlMeans repositories).

Files in this skill

  • SKILL.md11.3 KB
  • reference.md7 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…