@deepracticex/template
v1.1.0
Published
Template package demonstrating Deepractice package development standards
Downloads
19
Readme
@deepracticex/template
Deepractice Package Development Standards Template
This package serves as the standard template for all Deepractice packages. It demonstrates best practices for package structure, code organization, testing, and configuration.
Table of Contents
- Quick Start
- Architecture Standards
- Directory Structure
- Coding Standards
- Testing Standards
- Configuration Standards
- Publishing Standards
Quick Start
Creating a New Package
# 1. Copy this template
cp -r packages/template packages/your-package
# 2. Update package.json
cd packages/your-package
# Edit: name, description, keywords
# 3. Install dependencies (from monorepo root)
pnpm install
# 4. Start development
pnpm dev
# 5. Run tests
pnpm testArchitecture Standards
Layered Architecture
All packages MUST follow the three-layer architecture:
src/
├── api/ # Public API - what users import
├── types/ # Type definitions - exported to users
└── core/ # Internal implementation - NOT exportedLayer Responsibilities
api/ - Public API Layer
- Contains classes, functions, and utilities users directly call
- Stable interface - changes here affect users
- Example:
Logger,createLogger(), convenience functions
types/ - Type Definition Layer
- All TypeScript interfaces and types users need
- Configuration interfaces
- Public type exports
- Example:
LoggerConfig,LogLevel
core/ - Internal Implementation Layer
- Implementation details completely hidden from users
- Can be freely refactored without breaking changes
- Third-party library adapters
- Internal utilities
- Example: Pino adapter, caller tracking logic
Benefits
✅ Clear API Boundary - Users only see what they need
✅ Safe Refactoring - Change core/ without breaking users
✅ Better Testing - E2E test api/, unit test core/
✅ Type Safety - Types separated from implementation
Directory Structure
packages/your-package/
├── src/
│ ├── api/ # Public API
│ │ ├── *.ts # Implementation files
│ │ └── index.ts # Unified exports
│ ├── types/ # Type definitions
│ │ ├── *.ts # Type files
│ │ └── index.ts # Unified exports
│ ├── core/ # Internal implementation
│ │ ├── *.ts # Internal files
│ │ └── index.ts # Internal exports
│ └── index.ts # Package entry point
│
├── features/ # BDD scenarios (Gherkin)
│ └── *.feature # Feature files
│
├── tests/
│ ├── e2e/
│ │ ├── steps/ # Cucumber step definitions
│ │ │ └── *.steps.ts
│ │ └── support/ # Test support files
│ │ ├── world.ts # Shared test context
│ │ └── hooks.ts # Setup/teardown
│ └── unit/ # Unit tests (optional)
│
├── dist/ # Build output (gitignored)
├── tsconfig.json # TypeScript configuration
├── tsup.config.ts # Build configuration
├── cucumber.cjs # Cucumber configuration
├── package.json # Package manifest
└── README.md # Package documentationCoding Standards
1. Path Aliases
Two aliases for different contexts:
// ✅ src/ internal - use ~ alias
// src/api/example.ts
import type { ExampleConfig } from "~/types/config";
import { Processor } from "~/core/processor";
// ✅ tests/ access src/ - use @ alias
// tests/e2e/steps/example.steps.ts
import { createExample } from "@/index";
// tests/unit/core/processor.test.ts
import { Processor } from "@/core/processor";
// ❌ Wrong - relative paths
import { Example } from "../../../src/api/example";Alias Convention:
~/*- Internal use withinsrc/(~ means "home/internal")@/*- External access fromtests/(@ means "external reference")
Configuration:
tsconfig.json:baseUrl: "."+pathsfor both aliasestsup.config.ts:esbuildOptions.aliasfor build (only~needed)tsx: Native support for tsconfig paths- Uses
moduleResolution: "Bundler"- no.jsextension needed
2. Naming Conventions
Interface-First Naming (NOT Hungarian notation):
// ✅ Correct - interface gets the clean name
export interface Logger {}
export class DefaultLogger implements Logger {}
export class PinoLogger implements Logger {}
// ❌ Wrong - Hungarian notation
export interface ILogger {}
export class Logger implements ILogger {}Principles:
- Interface = simple, clean name (e.g.,
Logger) - Implementation = descriptive name (e.g.,
DefaultLogger,PinoLogger) - Avoid prefixes like
I,T,E
3. File Organization
One file, one type:
// example.ts - one class per file
export class Example {}
// config.ts - related types can group
export interface Config {}
export type ConfigOption = "a" | "b";Index files for exports:
// api/index.ts - unified public API
export { Example, createExample } from "~/api/example";
export { Helper } from "~/api/helper";
// types/index.ts - unified types
export type { Config } from "~/types/config";
export type { Result } from "~/types/result";4. Export Strategy
Main src/index.ts:
// Export public API
export * from "~/api/index";
// Export types separately to avoid duplication
export type { Config, Result } from "~/types/index";
// Default export (optional)
import { createExample } from "~/api/example";
export default createExample();DO NOT export core/ from package:
// ✅ Correct - only api and types
export * from "~/api/index";
export type * from "~/types/index";
// ❌ Wrong - exposing internal implementation
export * from "~/core/index"; // NEVER do thisTesting Standards
BDD with Cucumber
Feature files (features/*.feature):
Feature: Example Functionality
As a developer
I want to use the Example API
So that I can process data
Rule: Example should process correctly
Scenario: Process simple input
Given I have created an Example instance
When I execute with input "hello"
Then the result should be "Processed: hello"Step definitions (tests/e2e/steps/*.steps.ts):
import { Given, When, Then } from "@cucumber/cucumber";
import { expect } from "chai";
import { createExample } from "../../../src/index";
Given("I have created an Example instance", function () {
this.example = createExample();
});
When("I execute with input {string}", async function (input: string) {
this.result = await this.example.execute(input);
});
Then("the result should be {string}", function (expected: string) {
expect(this.result).to.equal(expected);
});Test scripts:
{
"scripts": {
"test": "NODE_OPTIONS='--import tsx' cucumber-js",
"test:dev": "NODE_OPTIONS='--import tsx' cucumber-js --profile dev",
"test:ci": "NODE_OPTIONS='--import tsx' cucumber-js --profile ci"
}
}Key points:
- Use
tsxinstead ofts-node(native tsconfig paths support) - Load via
NODE_OPTIONS='--import tsx' - BDD describes behavior in business language
- E2E tests verify the public API works
Configuration Standards
1. TypeScript Configuration
tsconfig.json:
{
"extends": "../typescript-config/base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"types": [],
"baseUrl": "./src",
"paths": {
"~/*": ["./*"]
}
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"]
}Key settings:
moduleResolution: "Bundler"(from base.json) - no.jsextensionbaseUrl+pathsfor~alias- Strict mode enabled
2. Build Configuration
tsup.config.ts:
import { defineConfig } from "tsup";
import path from "path";
export default defineConfig({
entry: ["src/index.ts"],
format: ["cjs", "esm"], // Dual format
dts: true, // Generate .d.ts
splitting: false,
sourcemap: true,
clean: true,
esbuildOptions(options) {
options.alias = {
"~": path.resolve(__dirname, "./src"),
};
},
});Output:
dist/index.js- ESMdist/index.cjs- CommonJSdist/index.d.ts- TypeScript definitions
3. Package Configuration
package.json essentials:
{
"type": "module",
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"files": ["dist", "package.json", "README.md"]
}Dependency Management:
- All tooling dependencies are managed in the root
package.json - Sub-packages do NOT declare devDependencies
- Dependencies are automatically available via pnpm workspace hoisting
- This ensures absolute version consistency across all packages
Available tools (auto-hoisted from root):
- TypeScript, tsup, tsx, rimraf
- Cucumber, Vitest, Chai
- Prettier, Lefthook
- All
@deepracticex/*config packages
When creating a new package:
- Copy the template
- Update name, description, keywords
- Run
pnpm installfrom root - Start coding - all tools are ready!
Publishing Standards
Before Publishing
Build the package:
pnpm buildRun tests:
pnpm testType check:
pnpm typecheckVerify exports:
# Check dist/ contains expected files ls -la dist/
Version Management
Use Changesets for version management:
# 1. Create changeset
pnpm changeset
# 2. Version packages (CI will do this)
pnpm changeset version
# 3. Publish (CI will do this)
pnpm changeset publishBest Practices
DO ✅
- Use layered architecture (api/types/core)
- Use
~path alias for internal imports - Use interface-first naming
- Write BDD scenarios before implementation
- Export only public API and types
- Keep core/ completely internal
- Use tsx for testing
- Generate proper TypeScript definitions
DON'T ❌
- Export from core/ layer
- Use Hungarian notation (ILogger)
- Use relative paths for internal imports
- Skip BDD scenarios
- Include
.jsextensions in imports (with Bundler resolution) - Use ts-node (use tsx instead)
- Forget to configure path aliases in tsup
Common Tasks
Add a new public function
- Implement in
core/if complex logic - Create wrapper in
api/ - Export from
api/index.ts - Export from
src/index.ts - Add BDD scenario in
features/ - Implement step definition in
tests/e2e/steps/
Add a new type
- Define in
types/*.ts - Export from
types/index.ts - Export from
src/index.tsas type-only
Refactor internal code
- Change anything in
core/freely - Keep
api/interface stable - Run tests to ensure no breakage
Questions?
This template embodies Deepractice package development standards. If you have questions or suggestions for improvements, please discuss with the team.
Key Principle: Make it easy to do the right thing.
Last updated: 2025-10-08
