Docs Packages
@procedures/procedure
Procedure scaffolding and meta-procedures for runtime procedure management.
Table of Contents#
Overview#
@procedures/procedure provides utilities for creating and managing procedures:
- procedure.new: Scaffold new procedures with types and registration boilerplate
- procedure.list: List all registered procedures in the runtime registry
- procedure.get: Get metadata for a specific procedure
- procedure.export: Export procedure definitions as JSON
This package simplifies procedure development by automating the creation of procedure files, type definitions, and registration code.
Installation#
npm install github:mark1russell7/client-procedure#main
Dependencies:
@procedures/client(peer dependency)zod^3.24.0
Architecture#
graph TB
subgraph "Procedure Scaffolding"
ProcNew[procedure.new]
Templates[Code Templates]
FileGen[File Generator]
end
subgraph "Generated Files"
ProcFile[src/procedures/namespace/operation.ts]
TypesFile[src/types.ts]
IndexFile[src/procedures/namespace/index.ts]
RegisterFile[src/register.ts]
end
subgraph "Meta-Procedures"
ProcList[procedure.list]
ProcGet[procedure.get]
ProcExport[procedure.export]
end
subgraph "Runtime Registry"
Registry[PROCEDURE_REGISTRY]
Procedures[Registered Procedures]
end
ProcNew --> Templates
Templates --> FileGen
FileGen --> ProcFile
FileGen --> TypesFile
FileGen --> IndexFile
ProcList --> Registry
ProcGet --> Registry
ProcExport --> Registry
Registry --> ProceduresScaffolding Flow#
sequenceDiagram
participant User
participant ProcNew as procedure.new
participant FS as File System
participant Templates
User->>ProcNew: procedure.new { name: "user.create" }
ProcNew->>Templates: Generate procedure code
Templates-->>ProcNew: Procedure file content
ProcNew->>FS: Write src/procedures/user/create.ts
ProcNew->>Templates: Generate types
Templates-->>ProcNew: Type definitions
ProcNew->>FS: Append to src/types.ts
ProcNew->>Templates: Generate exports
Templates-->>ProcNew: Export statement
ProcNew->>FS: Update src/procedures/user/index.ts
ProcNew->>FS: Add the procedure to src/register.ts
ProcNew-->>User: Success + next stepsThe scaffolded code compiles with the strict options of the workspace. A name whose path, file, type names or registration exists already is an error, and then nothing is written.
Quick Start#
import { Client, LocalTransport, PROCEDURE_REGISTRY } from "@procedures/client";
import "@procedures/procedure"; // registers its procedures
const client = new Client(new LocalTransport({ registry: PROCEDURE_REGISTRY }));
// Scaffold a new procedure
const result = await client.exec(["procedure", "new"], {
name: "user.create",
description: "Create a new user account",
});
console.log(result.operations);
// [
// "Creating directory: /project/src/procedures/user",
// "Creating procedure file: /project/src/procedures/user/create.ts",
// "Creating index: /project/src/procedures/user/index.ts",
// "Appending types to: /project/src/types.ts",
// "Next steps: ..."
// ]
API Reference#
Procedures#
procedure.new#
Scaffold a new procedure with type definitions and boilerplate.
interface ProcedureNewInput {
name: string; // Dot-notation name (e.g., "user.create" or "fs.readJson")
namespace?: string; // Namespace override: the folder under src/procedures (defaults to first segment)
description?: string; // Procedure description
path?: string; // Project path (defaults to cwd)
dryRun?: boolean; // Preview without creating files
}
interface ProcedureNewOutput {
success: boolean;
procedurePath: string[]; // e.g., ["user", "create"]
created: string[]; // Files created
modified: string[]; // Files modified
operations: string[]; // Operations performed
errors: string[]; // Any errors
}
Example:
const result = await client.exec(["procedure", "new"], {
name: "user.create",
description: "Create a new user",
path: "/path/to/package",
});
Generated Files:
The file of a procedure is src/procedures/<first segment>/<middle segments>/<last segment>.ts: api.v2.users.create gives src/procedures/api/v2/users/create.ts.
- Procedure File (
src/procedures/user/create.ts):
/**
* user.create procedure
*
* Create a new user
*/
import type { UserCreateInput, UserCreateOutput } from "../../types.js";
/**
* Create a new user
*/
export async function userCreate(_input: UserCreateInput): Promise<UserCreateOutput> {
// TODO: Implement user.create (rename _input to input when the code uses it)
return {
success: true,
message: "Hello from user.create",
};
}
- Type Definitions (appended to
src/types.ts, which getsimport { z } from "zod"if it has none, or is created):
// =============================================================================
// user.create Types - Create a new user
// =============================================================================
export const UserCreateInputSchema: z.ZodObject<{
// TODO: Add input fields
}> = z.object({
// TODO: Add input fields
});
export type UserCreateInput = z.infer<typeof UserCreateInputSchema>;
export interface UserCreateOutput {
/** Whether the operation succeeded */
success: boolean;
/** Response message */
message: string;
}
- Index Export (appended to
src/procedures/user/index.ts):
export { userCreate } from "./create.js";
- Registration (added to
src/register.ts: the imports, the definition, and the item of theregisterProcedures([...])array):
import { createProcedure, outputSchema, registerProcedures, zodAdapter } from "@procedures/client";
import { userCreate } from "./procedures/user/create.js";
import { UserCreateInputSchema, type UserCreateInput, type UserCreateOutput } from "./types.js";
const userCreateProcedure = createProcedure()
.path(["user", "create"])
.input(zodAdapter<UserCreateInput>(UserCreateInputSchema))
.output(outputSchema<UserCreateOutput>())
.meta({
description: "Create a new user",
args: [],
shorts: {},
output: "text",
})
.handler(async (input: UserCreateInput): Promise<UserCreateOutput> => userCreate(input))
.build();
export function register(): void {
registerProcedures([userCreateProcedure]);
}
If src/register.ts has no registerProcedures([...]) or registerAll([...]) call, the operations say what to add.
procedure.list#
List the procedures of the registry, with an optional namespace filter.
interface ProcedureListInput {
namespace?: string; // Only the procedures of this first path segment
includeMetadata?: boolean; // Default: false
}
interface ProcedureListOutput {
procedures: Array<{
path: string[];
key: string; // "git.commit"
description?: string;
tags?: string[];
hasHandler: boolean;
}>;
count: number;
}
const { procedures, count } = await client.exec(["procedure", "list"], { namespace: "git" });
procedure.get#
Get the metadata of one procedure.
interface ProcedureGetInput {
path: string[] | string; // ["git", "commit"] or "git.commit"
}
interface ProcedureGetOutput {
found: boolean;
path?: string[];
metadata?: Record<string, unknown>;
hasHandler?: boolean;
hasInputSchema?: boolean;
hasOutputSchema?: boolean;
}
procedure.export#
Export the path and the metadata of one procedure as a call ($proc).
interface ProcedureExportInput {
path: string[] | string;
}
interface ProcedureExportOutput {
success: boolean;
definition?: { $proc: string[]; metadata?: Record<string, unknown> };
error?: string; // When the procedure does not exist
}
[!NOTE] The core package
clientalso hasprocedure.listandprocedure.get, for the procedures thatprocedure.definemade.client-procedureregisters its own versions withoverride: true. Thus the result depends on whether the host importsclient-procedure:markand the MCP server do.
Examples#
Scaffold a Nested Procedure#
const result = await client.exec(["procedure", "new"], {
name: "api.users.permissions.grant",
description: "Grant permission to a user",
});
// Generated path: ["api", "users", "permissions", "grant"]
// File: src/procedures/api/users/permissions/grant.ts
Dry Run Mode#
// The plan of the scaffold, without a change of a file
const preview = await client.exec(["procedure", "new"], {
name: "product.update",
dryRun: true,
});
console.log(preview.operations);
Integration#
Use with CLI#
The procedure.new command is also available via the Mark CLI:
# Scaffold a new procedure
node packages/mark/dist/cli.js procedure new user.create --path packages/my-package
# With options
node packages/mark/dist/cli.js procedure new user.update \
--description "Update user information" \
--path packages/my-package \
--dry-run
Use in Package Development#
- Create package structure:
node packages/mark/dist/cli.js lib new my-client-package
pnpm install
- Scaffold procedures:
node packages/mark/dist/cli.js procedure new myapp.start --path packages/myapp
node packages/mark/dist/cli.js procedure new myapp.stop --path packages/myapp
node packages/mark/dist/cli.js procedure new myapp.status --path packages/myapp
Implement handlers: Edit the generated files in
src/procedures/myapp/*.ts.procedure newregistered each procedure insrc/register.ts.Build and test:
pnpm build
pnpm test
Naming Conventions#
Procedure names follow dot-notation:
- Single segment:
greet→["greet"] - Two segments:
user.create→["user", "create"] - Nested:
api.users.permissions.grant→["api", "users", "permissions", "grant"] - camelCase segments:
fs.readJson→["fs", "readJson"]
Generated identifiers:
- camelCase for function names:
user.create→userCreate - PascalCase for types:
user.create→UserCreate - Files: the last segment, in the folders of the other segments:
user.create→user/create.ts,api.users.permissions.grant→api/users/permissions/grant.ts
Two names can give the same identifiers (a.b.c and a.bC both give aBC). The second one is an error.
Requirements#
- Node.js: >= 22
- TypeScript: >= 5.0
- Dependencies:
@procedures/client(workspace:*)zod^3.24.0
License#
MIT
See Also#
@procedures/client- Core client/procedure system@procedures/mark- CLI that uses procedure.new for scaffolding- Package scaffolding guide in ecosystem documentation