Skip to content

Commit 5f48de3

Browse files
docs: godoc and package-doc pass (#161)
Add root and schema package doc.go files and document the highest-value author-facing contracts: the Processor lifecycle, ProcessedRecord error propagation semantics, and the standalone (WASM) vs built-in hosting model. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 6441632 commit 5f48de3

6 files changed

Lines changed: 204 additions & 1 deletion

File tree

doc.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Copyright © 2026 Meroxa, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package sdk is the Go SDK for building Conduit processors. A processor
16+
// receives records flowing through a pipeline, transforms them, and returns the
17+
// result. Authors implement the [Processor] interface (or adapt a function with
18+
// [NewProcessorFunc]) and the SDK handles the rest: configuration parsing,
19+
// schema encode/decode middleware, and the plumbing that connects the processor
20+
// to the Conduit engine.
21+
//
22+
// # Standalone vs. built-in processors
23+
//
24+
// The same [Processor] implementation can run in two ways:
25+
//
26+
// - Standalone: the processor is compiled to a WebAssembly module
27+
// (GOOS=wasip1 GOARCH=wasm) and executed by Conduit in a wazero runtime.
28+
// The module's main function calls [Run], which becomes the entry point.
29+
// This is the default and recommended mode — it isolates the processor from
30+
// the engine and lets it be distributed as a single portable binary.
31+
// - Built-in: the processor is compiled natively into a custom Conduit
32+
// build. This avoids the WebAssembly boundary (and its per-call
33+
// serialization cost) at the price of coupling the processor to the engine
34+
// binary. Built-in processors do not call [Run]; the engine invokes the
35+
// [Processor] methods directly.
36+
//
37+
// Author code is identical in both modes. Only the entry point and build
38+
// constraints differ, so write to the [Processor] contract and let the build
39+
// target decide how the processor is hosted.
40+
//
41+
// # Implementing a Processor
42+
//
43+
// Embed [UnimplementedProcessor] in your type. It provides no-op implementations
44+
// of the optional methods and satisfies the unexported marker method that keeps
45+
// the interface closed, so adding a method to [Processor] in a later release is
46+
// not a breaking change for existing processors:
47+
//
48+
// type myProcessor struct {
49+
// sdk.UnimplementedProcessor
50+
// cfg myConfig
51+
// }
52+
//
53+
// func (p *myProcessor) Specification() (sdk.Specification, error) { ... }
54+
// func (p *myProcessor) Configure(ctx context.Context, cfg config.Config) error { ... }
55+
// func (p *myProcessor) Process(ctx context.Context, recs []opencdc.Record) []sdk.ProcessedRecord { ... }
56+
//
57+
// # Lifecycle
58+
//
59+
// The runtime calls a processor's methods in a fixed order, and (for a single
60+
// processor instance) never concurrently — the standalone command loop in [Run]
61+
// processes one command at a time. A processor therefore does not need to guard
62+
// its own fields against concurrent access by the SDK, but it must not assume
63+
// any parallelism either.
64+
//
65+
// 1. Specification — called to discover the processor's name, version, and
66+
// configuration parameters. Must be side-effect free; it may be called
67+
// before Configure and without any configuration.
68+
// 2. Configure — called once with the user's configuration. Validate and store
69+
// it here. Do not open connections or start background work; that is Open's
70+
// job. See [ParseConfig] for turning the raw config map into a typed struct.
71+
// 3. Open — called once after Configure. Acquire resources and start any
72+
// background work here.
73+
// 4. Process — called repeatedly, once per incoming batch, until shutdown. See
74+
// the record-handling contract below.
75+
// 5. Teardown — called once when the pipeline is shutting down. No other method
76+
// is called after Teardown returns; the processor is then discarded. Release
77+
// everything Open acquired.
78+
//
79+
// Process may be called more than once with the same records (for example after
80+
// a restart when records were not flushed downstream), so processing must be
81+
// idempotent.
82+
//
83+
// # Record handling and error propagation
84+
//
85+
// Process receives a batch of [opencdc.Record] values and returns a
86+
// [ProcessedRecord] for each. The returned slice is positional: the result at
87+
// index i is the outcome of the input record at index i. Each input record may
88+
// carry raw or structured data in its key and payload; a processor that reads
89+
// structured fields should enable the schema-decode middleware (see below) or
90+
// handle both shapes.
91+
//
92+
// The concrete [ProcessedRecord] type an author returns decides how the record
93+
// continues through the pipeline:
94+
//
95+
// - [SingleRecord] — the transformed record continues downstream. This is the
96+
// common case.
97+
// - [MultiRecord] — the record is split into zero or more records. Returning
98+
// an empty MultiRecord is equivalent to [FilterRecord]; returning one record
99+
// is equivalent to [SingleRecord].
100+
// - [FilterRecord] — the record is acknowledged and dropped from the pipeline.
101+
// Use this to intentionally discard records; it is not an error.
102+
// - [ErrorRecord] — processing failed. The record is nacked and handled
103+
// according to the pipeline's error policy (for example routed to a dead-
104+
// letter queue or halting the pipeline). Returning an ErrorRecord is the
105+
// only way to signal a per-record failure — a processor must not drop a
106+
// record it could not process, or at-least-once delivery is violated.
107+
//
108+
// Because filtering, splitting, and failing are all expressed through the
109+
// return value rather than through the process's exit or a returned error,
110+
// Process itself does not return an error: a batch always produces a result for
111+
// every record it accounts for.
112+
//
113+
// # Middleware
114+
//
115+
// [DefaultProcessorMiddleware] wraps every processor run through [Run] with
116+
// schema decode and encode middleware. Decode middleware fetches the schema
117+
// referenced in a record's metadata and turns raw key/payload bytes into
118+
// [opencdc.StructuredData] before Process sees them; encode middleware reverses
119+
// that afterwards, so a processor can operate on structured data without dealing
120+
// with schema resolution. A processor tunes this via [Processor.MiddlewareOptions]
121+
// (see [ProcessorWithSchemaDecodeConfig] and [ProcessorWithSchemaEncodeConfig]).
122+
//
123+
// # Neighboring packages
124+
//
125+
// - github.com/conduitio/conduit-commons/opencdc defines the Record type that
126+
// flows through Process.
127+
// - github.com/conduitio/conduit-commons/config defines the configuration and
128+
// parameter types used in Specification and Configure.
129+
// - The schema subpackage is the author-facing API for creating and fetching
130+
// schemas from within a processor.
131+
// - The wasm and pprocutils subpackages are engine plumbing and are not meant
132+
// to be imported by processor authors.
133+
package sdk

errors.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,9 @@ var (
2323
"this action, please check the source code of the processor and make sure " +
2424
"all required processor methods are implemented")
2525

26+
// ErrFilterRecord is a sentinel error a function passed to
27+
// [NewProcessorFunc] can return to filter a record out of the pipeline
28+
// instead of failing it. The record is acked and dropped ([FilterRecord]),
29+
// not nacked. Returning any other error yields an [ErrorRecord].
2630
ErrFilterRecord = errors.New("filter out this record")
2731
)

processor_func.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ func NewProcessorFunc(specs Specification, f func(context.Context, opencdc.Recor
4747

4848
func (f ProcessorFunc) Specification() (Specification, error) { return f.specs, nil }
4949

50+
// Process applies the wrapped function to each record in order. A record for
51+
// which the function returns [ErrFilterRecord] becomes a [FilterRecord]; any
52+
// other error becomes an [ErrorRecord] and processing stops there, so the
53+
// returned slice is truncated at the first failing record and is shorter than
54+
// the input. Records before the error are returned as [SingleRecord] values;
55+
// records after it are left for Conduit to reprocess.
5056
func (f ProcessorFunc) Process(ctx context.Context, records []opencdc.Record) []ProcessedRecord {
5157
outRecs := make([]ProcessedRecord, len(records))
5258
for i, inRec := range records {

schema/doc.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright © 2026 Meroxa, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package schema is the processor-facing API for creating and fetching schemas.
16+
// Processors use it to register the schema of records they emit and to look up
17+
// the schema of records they receive, keeping schema identity consistent across
18+
// a pipeline.
19+
//
20+
// The package exposes two entry points, [Get] and [Create], both backed by the
21+
// package-level [SchemaService]. The service a processor talks to depends on how
22+
// it is hosted:
23+
//
24+
// - Standalone (WebAssembly): the engine replaces [SchemaService] at startup
25+
// with an implementation that forwards calls to Conduit's schema registry
26+
// over the host boundary, so schemas are shared with the rest of the
27+
// pipeline.
28+
// - Built-in / tests: the default [SchemaService] is an in-process,
29+
// [NewInMemoryService]-backed store wrapped in a cache. It has no
30+
// persistence and is not shared with a real registry — useful for unit
31+
// tests, not for cross-processor schema sharing.
32+
//
33+
// Get and Create results are cached, so repeated lookups of the same schema do
34+
// not cross the host boundary again.
35+
package schema

schema/in_memory.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ import (
2323
"github.com/conduitio/conduit-processor-sdk/pprocutils"
2424
)
2525

26+
// InMemoryService is a non-persistent [pprocutils.SchemaService] that keeps all
27+
// schemas in memory. It is the default backing store for tests and built-in
28+
// processors; it is safe for concurrent use but its contents are lost when the
29+
// process exits and are not shared with a real schema registry. Versions per
30+
// subject start at 1 and increment on each [InMemoryService.CreateSchema] call.
2631
type InMemoryService struct {
2732
// schemas is a map of schema subjects to all the versions of that schema
2833
// versioning starts at 1, newer versions are appended to the end of the versions slice.
@@ -33,6 +38,7 @@ type InMemoryService struct {
3338
idSequence int
3439
}
3540

41+
// NewInMemoryService returns an empty [InMemoryService] ready for use.
3642
func NewInMemoryService() *InMemoryService {
3743
return &InMemoryService{
3844
schemas: make(map[string][]schema.Schema),

schema/schema.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,31 @@ import (
2222
"github.com/conduitio/conduit-processor-sdk/pprocutils"
2323
)
2424

25+
// TypeAvro is the Avro schema type. It is currently the only type accepted by
26+
// [Create].
2527
const TypeAvro = schema.TypeAvro
2628

29+
// SchemaService is the service backing [Get] and [Create]. In a standalone
30+
// (WebAssembly) processor the engine overwrites this at startup with a client
31+
// for Conduit's schema registry; otherwise it defaults to an in-process,
32+
// cache-wrapped in-memory store. Replace it in tests to stub schema resolution.
2733
var SchemaService pprocutils.SchemaService = newCachedSchemaService(NewInMemoryService())
2834

2935
var (
36+
// ErrSubjectNotFound is returned by [Get] when no schema exists for the
37+
// requested subject.
3038
ErrSubjectNotFound = pprocutils.ErrSubjectNotFound
39+
// ErrVersionNotFound is returned by [Get] when the subject exists but the
40+
// requested version does not.
3141
ErrVersionNotFound = pprocutils.ErrVersionNotFound
32-
ErrInvalidSchema = pprocutils.ErrInvalidSchema
42+
// ErrInvalidSchema is returned by [Create] when the supplied bytes are not a
43+
// valid schema of the requested type.
44+
ErrInvalidSchema = pprocutils.ErrInvalidSchema
3345
)
3446

47+
// Get fetches the schema registered under the given subject and version.
48+
// Versions start at 1. It returns [ErrSubjectNotFound] or [ErrVersionNotFound]
49+
// (wrapped) if the schema is not registered.
3550
func Get(ctx context.Context, subject string, version int) (schema.Schema, error) {
3651
resp, err := SchemaService.GetSchema(ctx, pprocutils.GetSchemaRequest{
3752
Subject: subject,
@@ -43,6 +58,10 @@ func Get(ctx context.Context, subject string, version int) (schema.Schema, error
4358
return resp.Schema, nil
4459
}
4560

61+
// Create registers a new version of the schema for the given subject and
62+
// returns it with its assigned ID and version. Each call to Create appends a new
63+
// version; there is no deduplication of identical bytes. Only [TypeAvro] is
64+
// currently accepted — other types return [ErrInvalidSchema].
4665
func Create(ctx context.Context, typ schema.Type, subject string, bytes []byte) (schema.Schema, error) {
4766
resp, err := SchemaService.CreateSchema(ctx, pprocutils.CreateSchemaRequest{
4867
Subject: subject,

0 commit comments

Comments
 (0)