Skip to main content
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:
OptionDescriptionExample
(none)Generate Entity with template cones (default behavior)pnpm stub entity Product
--aiGenerate Entity with LLM-powered cones (ANTHROPIC_API_KEY required)pnpm stub entity Product --ai
--no-conesGenerate Entity only, skip cone generationpnpm 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:
PurposeDescriptionExample
API TestingTest external API callsp1-test-payment-api.ts
Data MigrationOne-time data transformationp2-migrate-user-data.ts
DebuggingValidate specific logicp3-debug-cache-issue.ts
Data GenerationCreate test datap4-create-sample-posts.ts
AnalysisCheck data statisticsp5-analyze-user-activity.ts

Automatic Numbering

Practice files are automatically numbered:
📁src/practices/
📄TSp1-first-practice.ts
📄TSp2-second-practice.ts
📄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:
  1. Production data: Practice uses real DB. Be careful!
  2. Backup: Always backup before important data operations.
  3. Transactions: Use transactions when modifying data.
  4. Testing: Test in development environment before production run.

Next Steps