AgentSkillsCN

create-implementation

为COPF概念编写TypeScript实现——即实现概念规范中所定义每项动作的处理器。涵盖存储模式、变体返回值、输入提取,以及测试环节。

SKILL.md
--- frontmatter
name: create-implementation
description: Write a TypeScript implementation for a COPF concept — the handler that implements each action defined in the concept spec. Covers storage patterns, variant returns, input extraction, and testing.
allowed-tools: Read, Grep, Glob, Edit, Write, Bash
argument-hint: "<concept-name>"

Create a COPF Concept Implementation

Write a TypeScript handler for the concept $ARGUMENTS that implements every action from its .concept spec.

What is a Concept Implementation?

A concept implementation is a handler object that provides one async function per action declared in the concept spec. Each function receives untyped input and a storage interface, and returns a variant completion (a discriminated union like { variant: 'ok', ... } or { variant: 'error', ... }).

typescript
import type { ConceptHandler } from '@copf/kernel';

export const myHandler: ConceptHandler = {
  async actionName(input, storage) {
    const field = input.field as string;
    // ... business logic using storage ...
    return { variant: 'ok', field };
  },
};

Implementations are pure business logic — they never reference other concepts. All cross-concept coordination happens through syncs.

Step-by-Step Process

Step 1: Read the Concept Spec

Every implementation must match its .concept spec exactly. The spec defines:

  • Type parameters — the generic types (e.g., [U] for user, [A] for article)
  • State — the relations (storage tables) the concept manages
  • Actions — the functions to implement, with their parameters and return variants
  • Invariants — behavioral contracts the implementation must satisfy

Find and read the spec from specs/app/<name>.concept or specs/framework/<name>.concept.

Example spec structure:

code
concept Article [A] {
  purpose { Manage articles with title, description, body, and author. }

  state {
    articles: set A
    slug: A -> String
    title: A -> String
    ...
  }

  actions {
    action create(article: A, title: String, ...) {
      -> ok(article: A) { Add article to set. Store all fields. }
    }
    action update(article: A, title: String, ...) {
      -> ok(article: A) { Update fields. }
      -> notfound(message: String) { If article does not exist. }
    }
  }

  invariant {
    after create(article: a, title: "Test", ...) -> ok(article: a)
    then get(article: a) -> ok(article: a, title: "Test", ...)
  }
}

Step 2: Create the Handler File

Place the file at:

code
implementations/typescript/app/<name>.impl.ts        # App concepts
implementations/typescript/framework/<name>.impl.ts   # Framework concepts

Start with the imports and handler skeleton:

typescript
import type { ConceptHandler } from '@copf/kernel';

export const <name>Handler: ConceptHandler = {
  // One async method per action from the spec
};

Step 3: Implement Each Action

For each action in the spec, write an async method on the handler object. Follow this pattern:

typescript
async actionName(input, storage) {
  // 1. Extract input fields with type assertions
  const field1 = input.field1 as string;
  const field2 = input.field2 as number;

  // 2. Business logic (validation, lookup, computation)

  // 3. Storage operations (read/write)

  // 4. Return a variant completion
  return { variant: 'ok', outputField: value };
},

Input Extraction

All inputs arrive as Record<string, unknown>. Cast each field to its TypeScript type:

Spec TypeTypeScript CastExample
Stringas stringinput.name as string
Intas numberinput.count as number
Floatas numberinput.rate as number
Boolas booleaninput.active as boolean
Bytesas string (base64)input.data as string
DateTimeas string (ISO 8601)input.created as string
ID, type paramas stringinput.user as string

Variant Returns

Return exactly the variants declared in the spec. The variant field is the discriminant:

typescript
// Spec: -> ok(user: U) { ... }
return { variant: 'ok', user };

// Spec: -> error(message: String) { ... }
return { variant: 'error', message: 'name already taken' };

// Spec: -> notfound(message: String) { ... }
return { variant: 'notfound', message: 'Article not found' };

// Spec: -> ok(valid: Bool) { ... }
return { variant: 'ok', valid: true };

Step 4: Wire Up Storage

Read references/storage-interface.md for the complete API.

Each concept gets its own isolated ConceptStorage. Storage is document-oriented — each relation from the spec maps to a storage collection.

Relation naming convention: The spec's state section defines relations. In implementations, use the entity name as the relation:

code
state { articles: set A; title: A -> String; ... }
→ storage relation: 'article'

state { hash: U -> Bytes; salt: U -> Bytes }
→ storage relation: 'password'

The key for each record is the type parameter value (the entity ID).

Storage Operations

typescript
// Create / Update
await storage.put('article', articleId, {
  article: articleId, slug, title, description, body, author,
  createdAt: now, updatedAt: now,
});

// Read one
const record = await storage.get('article', articleId);
if (!record) return { variant: 'notfound', message: 'Article not found' };

// Query by criteria
const matches = await storage.find('user', { email });
if (matches.length > 0) return { variant: 'error', message: 'email taken' };

// Delete one
await storage.del('article', articleId);

// Delete many by criteria
const count = await storage.delMany('comment', { target: articleId });

Step 5: Handle State Patterns

Read references/handler-anatomy.md for all patterns.

Common patterns that appear across implementations:

PatternWhen to useExample
Uniqueness checkBefore creating entitiesfind('user', { name }) before put()
Existence checkBefore update/delete/getget('article', id), return notfound if null
Read-modify-writeUpdating partial fieldsget(), spread {...existing, ...updates}, put()
Array mutationSet-valued relationsget(), push/filter array, put()
Derived fieldsComputed from inputsSlug from title, hash from password
TimestampsCreated/updated trackingnew Date().toISOString()

Step 6: Declare Capabilities

If the concept spec declares capabilities { requires crypto }, import from Node.js:

typescript
import { createHash, createHmac, randomBytes } from 'crypto';

Other capability patterns:

  • requires cryptoimport { createHash, randomBytes } from 'crypto'
  • requires fsimport { readFileSync } from 'fs'
  • No capabilities → No special imports needed

Step 7: Write Tests

Read references/action-dispatch.md for the testing framework.

Write tests in tests/<name>.test.ts or tests/<name>-flow.test.ts. Three test levels:

Unit test (handler in isolation)

typescript
import { describe, it, expect } from 'vitest';
import { createKernel } from '../implementations/typescript/framework/kernel-factory';
import { myHandler } from '../implementations/typescript/app/my.impl';

describe('My Concept', () => {
  it('performs action correctly', async () => {
    const kernel = createKernel();
    kernel.registerConcept('urn:copf/My', myHandler);

    const result = await kernel.invokeConcept('urn:copf/My', 'action', {
      field: 'value',
    });

    expect(result.variant).toBe('ok');
    expect(result.field).toBe('value');
  });
});

Invariant test (from spec)

The spec's invariant section defines behavioral contracts. Implement them as sequential action calls:

typescript
it('satisfies invariant: after set, check returns true', async () => {
  const kernel = createKernel();
  kernel.registerConcept('urn:copf/Password', passwordHandler);

  // AFTER clause
  const step1 = await kernel.invokeConcept('urn:copf/Password', 'set', {
    user: 'test-user', password: 'secret123',
  });
  expect(step1.variant).toBe('ok');

  // THEN clause
  const step2 = await kernel.invokeConcept('urn:copf/Password', 'check', {
    user: 'test-user', password: 'secret123',
  });
  expect(step2.variant).toBe('ok');
  expect(step2.valid).toBe(true);
});

For framework concepts with record/list literal invariants, the inputs arrive as nested objects. Ensure your handler correctly destructures them:

typescript
it('satisfies invariant: generates from manifest', async () => {
  const storage = createInMemoryStorage();
  // Record literal from the spec becomes a nested object input
  const result = await handler.generate({
    spec: 's1',
    manifest: {
      name: 'Ping', uri: 'urn:copf/Ping', typeParams: [], relations: [],
      actions: [{ name: 'ping', params: [],
        variants: [{ tag: 'ok', fields: [], prose: 'Pong.' }] }],
      invariants: [], graphqlSchema: '',
      jsonSchemas: { invocations: {}, completions: {} },
      capabilities: [], purpose: 'A test.',
    },
  }, storage);
  expect(result.variant).toBe('ok');
});

Flow test (with syncs)

typescript
it('processes full flow', async () => {
  const kernel = createKernel();
  kernel.registerConcept('urn:copf/My', myHandler);
  await kernel.loadSyncs(resolve(SYNCS_DIR, 'my.sync'));

  const response = await kernel.handleRequest({ method: 'my_action', ... });
  expect(response.body).toBeDefined();

  const flow = kernel.getFlowLog(response.flowId);
  expect(flow.length).toBeGreaterThanOrEqual(4);
});

Step 8: Register the Concept

In the application bootstrap, register with the kernel:

typescript
import { createKernel } from './kernel-factory';
import { myHandler } from './my.impl';

const kernel = createKernel();
kernel.registerConcept('urn:copf/My', myHandler);

For versioned concepts (with schema migration support):

typescript
await kernel.registerVersionedConcept('urn:copf/My', myHandler, 2);

Projection Sync Pattern — How Implementations Shape API Responses

When a concept action returns raw data (e.g., a user ID string for author), the response sync is responsible for enriching it before building the Web/respond body. This is called a projection sync — a response sync that uses where-clauses to resolve raw concept output into a richer shape.

Why this matters for implementations: If your action returns a foreign key (like author: userId), don't try to resolve it inside the handler — that would couple concepts. Instead, return the raw ID. The sync layer handles the enrichment.

Bad — coupling concepts in the implementation:

typescript
async list(_input, storage) {
  const articles = await storage.find('article');
  // DON'T: resolve author profile inside the article handler
  for (const a of articles) { a.author = await getProfile(a.author); }
  return { variant: 'ok', articles: JSON.stringify(articles) };
}

Good — return raw data, let syncs enrich it:

typescript
async list(_input, storage) {
  const articles = await storage.find('article');
  return { variant: 'ok', articles: JSON.stringify(articles) };
}

The projection sync then enriches the response using where-clause queries:

code
sync LoginResponse [eager]
when {
  Web/request: [ method: "login"; email: ?email ]
    => [ request: ?request ]
  JWT/generate: [ user: ?user ]
    => [ token: ?token ]
}
where {
  User: { ?u email: ?email; name: ?username }
}
then {
  Web/respond: [
    request: ?request;
    body: [
      user: [
        username: ?username;
        email: ?email;
        token: ?token ] ] ]
}

This LoginResponse sync is a projection — it combines JWT output (?token) with User state queries (?username) into a shaped response body. The concept handlers stay independent; the sync layer composes them.

The sync compiler validates field names (Section 7.2): if a sync references Article/list: [] => [ tagList: ?tags ] but Article/list only outputs articles, the compiler warns at compile time. This catches mismatches between what your implementation returns and what syncs expect.

See the /create-sync skill for the full projection sync pattern and examples.

Checklist

Before considering the implementation complete:

  • One async method per action in the spec
  • All input fields extracted with correct type casts
  • Every variant from the spec is returned on the appropriate code path
  • Storage relation names match the spec's state section
  • Uniqueness checks before creates (if spec mentions uniqueness)
  • Existence checks before updates/deletes/gets
  • Invariants from the spec pass as tests
  • No references to other concepts (all coordination via syncs)
  • Handler exported as const <name>Handler: ConceptHandler
  • Capabilities imported if spec declares them

Quick Reference

See references/handler-anatomy.md for the handler interface and all action patterns. See references/storage-interface.md for the full storage API. See references/action-dispatch.md for dispatch, testing, and registration. See examples/realworld-implementations.md for all RealWorld app implementations. See templates/implementation-scaffold.md for copy-paste templates.

Related Skills

SkillWhen to Use
/create-conceptDesign the concept spec that this implementation fulfills
/create-syncWrite syncs that invoke actions on this implementation
/create-storage-adapterWrite the storage backend this implementation uses
/create-transport-adapterWrite the transport that delivers actions to this implementation
/create-concept-kitBundle this implementation into a kit with its concept and syncs