The pnpm stub command auto-generates code templates to reduce repetitive work. You can quickly create Practice scripts or new Entities to speed up development.
Commands
practice - Generate Practice Script
Generate scripts for temporary experiments or data processing.
pnpm stub practice <name>
Example:
pnpm stub practice test-api
# Generated:
# src/practices/p1-test-api.ts
# Automatically opens in VS Code
# Run command copied to clipboard
Generated file:
src/practices/p1-test-api.ts
import { Sonamu } from "sonamu";
console.clear();
console.log("p1-test-api.ts");
Sonamu.runScript(async () => {
// TODO
});
Run:
# Paste from clipboard (Cmd+V)
pnpm node -r dotenv/config --enable-source-maps dist/practices/p1-test-api.js
entity - Generate Entity
Create a new Entity. By default, template cones are automatically generated along with the Entity.
pnpm stub entity <entityId> [options]
Options:
| Option | Description | Example |
|---|
| (none) | Generate Entity with template cones (default behavior) | pnpm stub entity Product |
--ai | Generate Entity with LLM-powered cones (ANTHROPIC_API_KEY required) | pnpm stub entity Product --ai |
--no-cones | Generate Entity only, skip cone generation | pnpm stub entity Product --no-cones |
Examples:
# Default: Generate Entity with template cones
pnpm stub entity Product
# ✓ Entity 'Product' created with template cones
# 💡 Tip: Run 'pnpm sonamu cone gen Product' to improve with AI
# Generate with AI-powered cones
pnpm stub entity Product --ai
# ✓ Entity 'Product' created
# 🌟 Generating AI-powered cones...
# ✅ Done (1234 tokens)
# Generate Entity without cones
pnpm stub entity Product --no-cones
# ✓ Entity 'Product' created without cones
Template cones can be generated without ANTHROPIC_API_KEY. You can later upgrade them using the cone gen command with AI.
Generated file:
src/entities/Product.entity.ts
import type { EntityType } from "sonamu";
export const ProductEntity = {
properties: [
{
name: "id",
type: "int",
primaryKey: true,
autoIncrement: true,
},
{
name: "title",
type: "string",
length: 255,
},
{
name: "created_at",
type: "datetime",
default: "CURRENT_TIMESTAMP",
},
],
} satisfies EntityType;
Practice Scripts
Use Cases
Practice scripts are useful for these tasks:
| Purpose | Description | Example |
|---|
| API Testing | Test external API calls | p1-test-payment-api.ts |
| Data Migration | One-time data transformation | p2-migrate-user-data.ts |
| Debugging | Validate specific logic | p3-debug-cache-issue.ts |
| Data Generation | Create test data | p4-create-sample-posts.ts |
| Analysis | Check data statistics | p5-analyze-user-activity.ts |
Automatic Numbering
Practice files are automatically numbered:
📁src/practices/
📄TSp3-third-practice.ts - Next is p4
When you create a new practice, it automatically gets the highest number + 1.
Practical Examples
1. API Testing
src/practices/p1-test-payment-api.ts
import { Sonamu } from "sonamu";
console.clear();
console.log("p1-test-payment-api.ts");
Sonamu.runScript(async () => {
const paymentService = new PaymentService();
// Test payment attempt
const result = await paymentService.charge({
amount: 10000,
orderId: "TEST-001",
});
console.log("Payment result:", result);
});
2. Data Migration
src/practices/p2-migrate-user-data.ts
import { Sonamu } from "sonamu";
import { UserModel } from "../models/User.model";
console.clear();
console.log("p2-migrate-user-data.ts");
Sonamu.runScript(async () => {
// Get all users
const users = await UserModel.findAll();
for (const user of users) {
// Migrate legacy field to new field
await UserModel.update(user.id, {
new_field: transformData(user.legacy_field),
});
console.log(`✓ Migrated user ${user.id}`);
}
console.log(`\n✓ Total: ${users.length} users migrated`);
});
3. Batch Jobs
src/practices/p3-send-welcome-emails.ts
import { Sonamu } from "sonamu";
import { UserModel } from "../models/User.model";
console.clear();
console.log("p3-send-welcome-emails.ts");
Sonamu.runScript(async () => {
// New users
const newUsers = await UserModel.findMany({
where: { welcome_email_sent: false },
});
console.log(`Sending welcome emails to ${newUsers.length} users...`);
for (const user of newUsers) {
try {
await sendWelcomeEmail(user.email);
await UserModel.update(user.id, {
welcome_email_sent: true,
});
console.log(`✓ Sent to ${user.email}`);
} catch (error) {
console.error(`✗ Failed to send to ${user.email}:`, error);
}
}
});
Entity Generation
Basic Structure
Entities generated with stub entity include basic fields:
export const ProductEntity = {
properties: [
{ name: "id", type: "int", primaryKey: true, autoIncrement: true },
{ name: "title", type: "string", length: 255 },
{ name: "created_at", type: "datetime", default: "CURRENT_TIMESTAMP" },
],
} satisfies EntityType;
Customization
Add needed fields after generation:
export const ProductEntity = {
properties: [
// Basic fields
{ name: "id", type: "int", primaryKey: true, autoIncrement: true },
{ name: "title", type: "string", length: 255 },
// Additional fields
{ name: "description", type: "text", nullable: true },
{ name: "price", type: "decimal", precision: 10, scale: 2 },
{ name: "stock", type: "int", default: 0 },
{ name: "category_id", type: "int" },
// Timestamps
{ name: "created_at", type: "datetime", default: "CURRENT_TIMESTAMP" },
{ name: "updated_at", type: "datetime", onUpdate: "CURRENT_TIMESTAMP" },
],
indexes: [
{ fields: ["category_id"] },
{ fields: ["title"], type: "fulltext" },
],
belongsTo: [
{ entityId: "Category", as: "category" },
],
} satisfies EntityType;
Development Workflow
1. Validate Ideas
# Quickly create practice
pnpm stub practice test-idea
# Write code and run
pnpm node -r dotenv/config --enable-source-maps dist/practices/p1-test-idea.js
# Delete or keep after verification
2. Add Entity
# Generate Entity
pnpm stub entity Order
# Write Entity definition
# src/entities/Order.entity.ts
# Create and apply migration
pnpm migrate run
# Scaffold Model
pnpm scaffold model Order
3. Data Operations
# Generate Practice script
pnpm stub practice import-orders
# Write data processing logic
# src/practices/p1-import-orders.ts
# Run
pnpm node -r dotenv/config --enable-source-maps dist/practices/p1-import-orders.js
Practical Tips
1. Reuse Practice Scripts
// ✅ Make functions reusable
async function processUsers(filter: any) {
const users = await UserModel.findMany({ where: filter });
// Processing logic...
}
Sonamu.runScript(async () => {
// Reuse with various filters
await processUsers({ role: "admin" });
await processUsers({ role: "user" });
});
2. Error Handling
Sonamu.runScript(async () => {
try {
await riskyOperation();
} catch (error) {
console.error("Error occurred:", error);
process.exit(1); // Exit on failure
}
});
3. Progress Display
Sonamu.runScript(async () => {
const users = await UserModel.findAll();
for (let i = 0; i < users.length; i++) {
await processUser(users[i]);
// Show progress
const progress = Math.round(((i + 1) / users.length) * 100);
console.log(`Progress: ${progress}% (${i + 1}/${users.length})`);
}
});
4. Cleanup Practices
# Delete completed practices
rm src/practices/p1-old-practice.ts
# Or move to archive directory
mkdir src/practices/archived
mv src/practices/p1-*.ts src/practices/archived/
Cautions
Cautions when using Practice scripts:
- Production data: Practice uses real DB. Be careful!
- Backup: Always backup before important data operations.
- Transactions: Use transactions when modifying data.
- Testing: Test in development environment before production run.
Next Steps