Files
naudachu 83f73c5cea refactor: turn the repo into a two-plugin marketplace
tea and tdl were two repositories, each carrying its own
.claude-plugin/marketplace.json — two marketplaces to register for what
is one collection. Fold them into one.

The repo root is now the marketplace and nothing else: a single
.claude-plugin/marketplace.json whose entries point at ./plugins/tea and
./plugins/tdl. A plugin's root is its own directory under plugins/, so
${CLAUDE_PLUGIN_ROOT} still resolves inside it and every path a plugin
uses stays relative to itself — the hooks and the test roots needed no
adjustment beyond the move.

tea's files move with git mv, so its history and blame follow. tdl
arrives as a plain copy; its history stays in claude-skills/threedotslab.

test_payload_root asserted `tmp/` was ignored by REPO/.gitignore. The
rule is that tmp/ is ignored, not which file says so, and git reads every
.gitignore on the way up — so the test now walks up to the repo root the
same way git does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 00:25:28 +05:00

2.9 KiB

Query Handler Scaffold Template

Generate a query handler file with a read model interface.

Placeholders

  • {{Name}} — PascalCase query name (e.g., AvailableHours)
  • {{name}} — camelCase (e.g., availableHours)
  • {{name_snake}} — snake_case (e.g., available_hours)
  • {{module}} — Go module path from go.mod
  • {{Result}} — Result type (e.g., []Date, *HourDetails)

File: app/query/{{name_snake}}.go

package query

import (
	"context"

	"github.com/sirupsen/logrus"

	"{{module_common}}/decorator"
)

// Read model — defines what data the query needs
// Implemented by adapters (repository or dedicated read store)
type {{Name}}ReadModel interface {
	{{Name}}(ctx context.Context /* TODO: add query params */) ({{Result}}, error)
}

// 1. Query struct — noun phrase, plain data
type {{Name}} struct {
	// TODO: Add query parameters
	// Example:
	// From time.Time
	// To   time.Time
}

// Result types — optimized for reading, may differ from domain entities
// type Date struct {
// 	Date  time.Time
// 	Hours []Hour
// }

// 2. Exported handler type alias
type {{Name}}Handler decorator.QueryHandler[{{Name}}, {{Result}}]

// 3. Unexported concrete handler struct
type {{name}}Handler struct {
	readModel {{Name}}ReadModel
}

// 4. Constructor with nil-checks + decorator wrapping
func New{{Name}}Handler(
	readModel {{Name}}ReadModel,
	logger *logrus.Entry,
	metricsClient decorator.MetricsClient,
) {{Name}}Handler {
	if readModel == nil {
		panic("nil readModel")
	}
	if logger == nil {
		panic("nil logger")
	}
	if metricsClient == nil {
		panic("nil metricsClient")
	}

	return decorator.ApplyQueryDecorators[{{Name}}, {{Result}}](
		{{name}}Handler{readModel: readModel},
		logger,
		metricsClient,
	)
}

// Handle — delegates to read model, may add input validation
func (h {{name}}Handler) Handle(ctx context.Context, q {{Name}}) ({{Result}}, error) {
	// TODO: Add input validation if needed
	// Example:
	// if q.From.After(q.To) {
	//     return nil, errors.NewIncorrectInputError("date-from-after-date-to", "date from is after date to")
	// }

	return h.readModel.{{Name}}(ctx /* TODO: pass query params */)
}

Update app/app.go

Add to the Queries struct:

type Queries struct {
	// ... existing handlers ...
	{{Name}} query.{{Name}}Handler
}

Update service/application.go

Wire the handler. The read model is typically implemented by the same repository adapter or a dedicated read adapter:

Queries: app.Queries{
	// ... existing handlers ...
	{{Name}}: query.New{{Name}}Handler(
		{{entity}}Repository,  // implements {{Name}}ReadModel
		logger,
		metricsClient,
	),
},

Implement ReadModel on Adapter

Add the read model method to your repository adapter:

// In adapters/
func (r *Memory{{Entity}}Repository) {{Name}}(ctx context.Context /* params */) ({{Result}}, error) {
	// TODO: Implement query against storage
}