@zigrivers/scaffold 3.7.0 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +113 -8
  2. package/content/knowledge/browser-extension/browser-extension-architecture.md +195 -0
  3. package/content/knowledge/browser-extension/browser-extension-content-scripts.md +264 -0
  4. package/content/knowledge/browser-extension/browser-extension-conventions.md +156 -0
  5. package/content/knowledge/browser-extension/browser-extension-cross-browser.md +229 -0
  6. package/content/knowledge/browser-extension/browser-extension-dev-environment.md +247 -0
  7. package/content/knowledge/browser-extension/browser-extension-manifest.md +220 -0
  8. package/content/knowledge/browser-extension/browser-extension-project-structure.md +183 -0
  9. package/content/knowledge/browser-extension/browser-extension-requirements.md +107 -0
  10. package/content/knowledge/browser-extension/browser-extension-security.md +202 -0
  11. package/content/knowledge/browser-extension/browser-extension-service-workers.md +265 -0
  12. package/content/knowledge/browser-extension/browser-extension-store-submission.md +155 -0
  13. package/content/knowledge/browser-extension/browser-extension-testing.md +270 -0
  14. package/content/knowledge/data-pipeline/data-pipeline-architecture.md +175 -0
  15. package/content/knowledge/data-pipeline/data-pipeline-batch-patterns.md +263 -0
  16. package/content/knowledge/data-pipeline/data-pipeline-conventions.md +176 -0
  17. package/content/knowledge/data-pipeline/data-pipeline-dev-environment.md +350 -0
  18. package/content/knowledge/data-pipeline/data-pipeline-orchestration.md +291 -0
  19. package/content/knowledge/data-pipeline/data-pipeline-project-structure.md +257 -0
  20. package/content/knowledge/data-pipeline/data-pipeline-quality.md +324 -0
  21. package/content/knowledge/data-pipeline/data-pipeline-requirements.md +145 -0
  22. package/content/knowledge/data-pipeline/data-pipeline-schema-management.md +295 -0
  23. package/content/knowledge/data-pipeline/data-pipeline-security.md +326 -0
  24. package/content/knowledge/data-pipeline/data-pipeline-streaming-patterns.md +280 -0
  25. package/content/knowledge/data-pipeline/data-pipeline-testing.md +406 -0
  26. package/content/knowledge/library/library-api-design.md +306 -0
  27. package/content/knowledge/library/library-architecture.md +247 -0
  28. package/content/knowledge/library/library-bundling.md +244 -0
  29. package/content/knowledge/library/library-conventions.md +229 -0
  30. package/content/knowledge/library/library-dev-environment.md +220 -0
  31. package/content/knowledge/library/library-documentation.md +300 -0
  32. package/content/knowledge/library/library-project-structure.md +237 -0
  33. package/content/knowledge/library/library-requirements.md +173 -0
  34. package/content/knowledge/library/library-security.md +257 -0
  35. package/content/knowledge/library/library-testing.md +319 -0
  36. package/content/knowledge/library/library-type-definitions.md +284 -0
  37. package/content/knowledge/library/library-versioning.md +300 -0
  38. package/content/knowledge/ml/ml-architecture.md +172 -0
  39. package/content/knowledge/ml/ml-conventions.md +209 -0
  40. package/content/knowledge/ml/ml-dev-environment.md +299 -0
  41. package/content/knowledge/ml/ml-experiment-tracking.md +285 -0
  42. package/content/knowledge/ml/ml-model-evaluation.md +256 -0
  43. package/content/knowledge/ml/ml-observability.md +253 -0
  44. package/content/knowledge/ml/ml-project-structure.md +216 -0
  45. package/content/knowledge/ml/ml-requirements.md +138 -0
  46. package/content/knowledge/ml/ml-security.md +188 -0
  47. package/content/knowledge/ml/ml-serving-patterns.md +243 -0
  48. package/content/knowledge/ml/ml-testing.md +301 -0
  49. package/content/knowledge/ml/ml-training-patterns.md +269 -0
  50. package/content/knowledge/mobile-app/mobile-app-architecture.md +283 -0
  51. package/content/knowledge/mobile-app/mobile-app-conventions.md +180 -0
  52. package/content/knowledge/mobile-app/mobile-app-deployment.md +298 -0
  53. package/content/knowledge/mobile-app/mobile-app-dev-environment.md +257 -0
  54. package/content/knowledge/mobile-app/mobile-app-distribution.md +264 -0
  55. package/content/knowledge/mobile-app/mobile-app-observability.md +317 -0
  56. package/content/knowledge/mobile-app/mobile-app-offline-patterns.md +311 -0
  57. package/content/knowledge/mobile-app/mobile-app-project-structure.md +245 -0
  58. package/content/knowledge/mobile-app/mobile-app-push-notifications.md +321 -0
  59. package/content/knowledge/mobile-app/mobile-app-requirements.md +147 -0
  60. package/content/knowledge/mobile-app/mobile-app-security.md +338 -0
  61. package/content/knowledge/mobile-app/mobile-app-testing.md +400 -0
  62. package/content/methodology/browser-extension-overlay.yml +82 -0
  63. package/content/methodology/data-pipeline-overlay.yml +70 -0
  64. package/content/methodology/library-overlay.yml +67 -0
  65. package/content/methodology/ml-overlay.yml +70 -0
  66. package/content/methodology/mobile-app-overlay.yml +71 -0
  67. package/dist/cli/commands/init.d.ts +22 -0
  68. package/dist/cli/commands/init.d.ts.map +1 -1
  69. package/dist/cli/commands/init.js +202 -3
  70. package/dist/cli/commands/init.js.map +1 -1
  71. package/dist/cli/commands/init.test.js +190 -0
  72. package/dist/cli/commands/init.test.js.map +1 -1
  73. package/dist/config/schema.d.ts +1456 -80
  74. package/dist/config/schema.d.ts.map +1 -1
  75. package/dist/config/schema.js +87 -0
  76. package/dist/config/schema.js.map +1 -1
  77. package/dist/config/schema.test.js +312 -3
  78. package/dist/config/schema.test.js.map +1 -1
  79. package/dist/core/assembly/overlay-loader.test.js +55 -0
  80. package/dist/core/assembly/overlay-loader.test.js.map +1 -1
  81. package/dist/e2e/project-type-overlays.test.d.ts +2 -1
  82. package/dist/e2e/project-type-overlays.test.d.ts.map +1 -1
  83. package/dist/e2e/project-type-overlays.test.js +780 -14
  84. package/dist/e2e/project-type-overlays.test.js.map +1 -1
  85. package/dist/types/config.d.ts +16 -1
  86. package/dist/types/config.d.ts.map +1 -1
  87. package/dist/wizard/questions.d.ts +28 -1
  88. package/dist/wizard/questions.d.ts.map +1 -1
  89. package/dist/wizard/questions.js +127 -1
  90. package/dist/wizard/questions.js.map +1 -1
  91. package/dist/wizard/questions.test.js +224 -4
  92. package/dist/wizard/questions.test.js.map +1 -1
  93. package/dist/wizard/wizard.d.ts +22 -0
  94. package/dist/wizard/wizard.d.ts.map +1 -1
  95. package/dist/wizard/wizard.js +28 -1
  96. package/dist/wizard/wizard.js.map +1 -1
  97. package/package.json +1 -1
@@ -0,0 +1,306 @@
1
+ ---
2
+ name: library-api-design
3
+ description: Public surface design, method signatures, error contracts, and extension points for published library APIs
4
+ topics: [library, api-design, public-surface, method-signatures, error-contracts, extension-points]
5
+ ---
6
+
7
+ Library API design is the highest-leverage activity in library development. A well-designed API makes correct usage easy and incorrect usage hard, survives multiple major versions without fundamental restructuring, and communicates intent through its shape alone. Poor API design cannot be fixed without breaking changes — every naming mistake, parameter order error, and missing overload becomes permanent once consumers adopt it. Design APIs from the consumer's perspective first, implementation second.
8
+
9
+ ## Summary
10
+
11
+ Design APIs by writing consumer call sites before writing implementation. Prefer named options objects over positional parameters beyond two arguments. Return values should be typed as specifically as possible — avoid returning `any` or overly wide union types. Error contracts must be explicit: document what each function throws, when, and why. Provide extension points through composition (options injection, middleware, plugins) rather than inheritance. Make the happy path obvious and the error path impossible to ignore.
12
+
13
+ Core principles:
14
+ - Pit-of-success design: the obvious way to use the API is the correct way
15
+ - Named options objects for 3+ parameters
16
+ - Explicit error contracts (typed throws, documented in JSDoc)
17
+ - Overloads for genuinely different call signatures
18
+ - Consistent return type patterns (never `T | undefined` when you can overload)
19
+
20
+ ## Deep Guidance
21
+
22
+ ### Write Call Sites First
23
+
24
+ Before implementing any function, write the code that will call it:
25
+
26
+ ```typescript
27
+ // Step 1: Write how consumers will use this
28
+ import { parseConfig } from 'my-library'
29
+
30
+ // Use case 1: parse a string, get typed config
31
+ const config = parseConfig(rawString)
32
+
33
+ // Use case 2: parse with strict mode
34
+ const config = parseConfig(rawString, { strict: true })
35
+
36
+ // Use case 3: parse a file path (different input)
37
+ const config = await parseConfigFile('./config.toml')
38
+
39
+ // Use case 4: handle parse errors gracefully
40
+ try {
41
+ const config = parseConfig(rawString)
42
+ } catch (err) {
43
+ if (err instanceof ParseError) {
44
+ console.error(`Parse failed at line ${err.line}: ${err.message}`)
45
+ }
46
+ }
47
+
48
+ // Step 2: Now design the API to make this work naturally
49
+ export function parseConfig(input: string, options?: ParseOptions): Config
50
+ export async function parseConfigFile(path: string, options?: ParseOptions): Promise<Config>
51
+ ```
52
+
53
+ This technique reveals usability issues before any code is written.
54
+
55
+ ### Options Object Pattern
56
+
57
+ Positional parameters beyond two create cognitive load and fragile call sites:
58
+
59
+ ```typescript
60
+ // BAD: positional parameters — order is arbitrary, easy to mix up
61
+ function connect(host: string, port: number, timeout: number, ssl: boolean, retries: number): Client
62
+
63
+ // Called as: connect('localhost', 5432, 30000, true, 3)
64
+ // Which is timeout and which is retries? Must check signature every time.
65
+
66
+ // GOOD: named options object
67
+ interface ConnectOptions {
68
+ host: string
69
+ port: number
70
+ timeout?: number // ms, default: 30000
71
+ ssl?: boolean // default: false
72
+ retries?: number // default: 3
73
+ }
74
+
75
+ function connect(options: ConnectOptions): Client
76
+ // connect({ host: 'localhost', port: 5432, ssl: true })
77
+ // Self-documenting call site. New options add without breaking callers.
78
+ ```
79
+
80
+ **Options object rules:**
81
+ - All options beyond the first two required parameters go in an options object
82
+ - Options should have sensible defaults (document the defaults in JSDoc)
83
+ - Required options stay required; don't make everything optional
84
+ - Never use boolean flags that change behavior fundamentally — use discriminated unions
85
+
86
+ ```typescript
87
+ // BAD: boolean flag that means completely different behavior
88
+ function parse(input: string, isFile: boolean): Config
89
+
90
+ // GOOD: separate functions or discriminated union
91
+ function parseString(input: string): Config
92
+ function parseFile(path: string): Promise<Config>
93
+ // Or:
94
+ type ParseInput = { type: 'string'; value: string } | { type: 'file'; path: string }
95
+ function parse(input: ParseInput): Config | Promise<Config>
96
+ ```
97
+
98
+ ### Method Signature Design
99
+
100
+ **Overloads for genuinely different signatures:**
101
+ ```typescript
102
+ // Overloads allow TypeScript to narrow the return type based on input
103
+ function parse(input: string): Config
104
+ function parse(input: string, options: { async: true }): Promise<Config>
105
+ function parse(input: string, options: { async: false }): Config
106
+ function parse(input: string, options?: ParseOptions): Config | Promise<Config> {
107
+ // implementation
108
+ }
109
+ ```
110
+
111
+ Use overloads sparingly. Each overload is a commitment to maintain that signature. If you find yourself needing many overloads, reconsider whether the API should be split into separate functions.
112
+
113
+ **Generic constraints — add them when they add value:**
114
+ ```typescript
115
+ // Good: generic constraint enables type narrowing
116
+ function pick<T extends object, K extends keyof T>(obj: T, keys: K[]): Pick<T, K>
117
+
118
+ // Bad: generic without constraint is less type-safe
119
+ function pick<T, K>(obj: T, keys: K[]): any
120
+
121
+ // Bad: generic where it adds no value (always the same type)
122
+ function identity<T>(value: T): T // Fine for teaching, rarely needed in practice
123
+ ```
124
+
125
+ **Return type specificity:**
126
+ ```typescript
127
+ // BAD: too wide
128
+ function getUser(id: string): object
129
+
130
+ // BAD: any
131
+ function parseYAML(input: string): any
132
+
133
+ // GOOD: specific types
134
+ function getUser(id: string): User | null // null when not found
135
+ function parseYAML<T = unknown>(input: string): T // generic with default
136
+
137
+ // BEST when the shape is known:
138
+ interface User {
139
+ id: string
140
+ name: string
141
+ email: string
142
+ createdAt: Date
143
+ }
144
+ function getUser(id: string): User | null
145
+ ```
146
+
147
+ ### Error Contracts
148
+
149
+ Every public function must have a documented error contract. Document errors in JSDoc and in a separate errors section of the API documentation.
150
+
151
+ **JSDoc error documentation:**
152
+ ```typescript
153
+ /**
154
+ * Parse a configuration string into a typed Config object.
155
+ *
156
+ * @param input - TOML-formatted configuration string
157
+ * @param options - Parsing options
158
+ * @returns Parsed and validated Config object
159
+ *
160
+ * @throws {ParseError} If the input string is not valid TOML.
161
+ * `ParseError.line` and `ParseError.column` indicate the error location.
162
+ * @throws {ValidationError} If the parsed config fails schema validation.
163
+ * `ValidationError.errors` contains the list of validation failures.
164
+ *
165
+ * @example
166
+ * ```typescript
167
+ * try {
168
+ * const config = parseConfig('[server]\nhost = "localhost"')
169
+ * } catch (err) {
170
+ * if (err instanceof ParseError) {
171
+ * console.error(`Syntax error at line ${err.line}`)
172
+ * }
173
+ * }
174
+ * ```
175
+ */
176
+ export function parseConfig(input: string, options?: ParseOptions): Config
177
+ ```
178
+
179
+ **Result type pattern (alternative to throws):**
180
+ For APIs where errors are expected and should be handled inline, a Result type is cleaner than throws:
181
+
182
+ ```typescript
183
+ export type Result<T, E extends Error = Error> =
184
+ | { ok: true; value: T }
185
+ | { ok: false; error: E }
186
+
187
+ export function tryParseConfig(input: string): Result<Config, ParseError | ValidationError> {
188
+ try {
189
+ return { ok: true, value: parseConfig(input) }
190
+ } catch (err) {
191
+ return { ok: false, error: err as ParseError | ValidationError }
192
+ }
193
+ }
194
+
195
+ // Consumer usage:
196
+ const result = tryParseConfig(input)
197
+ if (result.ok) {
198
+ console.log(result.value.server.host)
199
+ } else {
200
+ console.error(result.error.message)
201
+ }
202
+ ```
203
+
204
+ Provide both patterns when the use case warrants: throwing for "should not fail" paths, Result type for "expected to sometimes fail" paths.
205
+
206
+ ### Extension Points
207
+
208
+ Design extension points that don't require forking or subclassing:
209
+
210
+ **Middleware pattern for transform pipelines:**
211
+ ```typescript
212
+ export interface ParseMiddleware {
213
+ (input: string, next: (input: string) => Config): Config
214
+ }
215
+
216
+ export function createParser(middlewares: ParseMiddleware[] = []): Parser {
217
+ return {
218
+ parse(input: string): Config {
219
+ const chain = middlewares.reduceRight(
220
+ (next: (i: string) => Config, mw) => (i: string) => mw(i, next),
221
+ parseRaw
222
+ )
223
+ return chain(input)
224
+ }
225
+ }
226
+ }
227
+
228
+ // Consumer adds preprocessing:
229
+ const parser = createParser([
230
+ (input, next) => next(input.trim().toLowerCase()),
231
+ (input, next) => {
232
+ const result = next(input)
233
+ return { ...result, source: 'custom' }
234
+ }
235
+ ])
236
+ ```
237
+
238
+ **Hook pattern for lifecycle events:**
239
+ ```typescript
240
+ export interface ClientHooks {
241
+ beforeRequest?: (req: Request) => Request | Promise<Request>
242
+ afterResponse?: (res: Response) => Response | Promise<Response>
243
+ onError?: (err: Error) => void
244
+ }
245
+
246
+ export function createClient(options: ClientOptions & { hooks?: ClientHooks }): Client
247
+ ```
248
+
249
+ **Avoid class inheritance as extension:**
250
+ ```typescript
251
+ // BAD: forces consumers to subclass
252
+ class BaseClient {
253
+ protected abstract buildRequest(options: RequestOptions): Request
254
+ // Consumers must extend to customize
255
+ }
256
+
257
+ // GOOD: inject the behavior
258
+ type RequestBuilder = (options: RequestOptions) => Request
259
+
260
+ function createClient(options: {
261
+ buildRequest?: RequestBuilder
262
+ }): Client
263
+ ```
264
+
265
+ Subclassing creates tight coupling between the consumer and the library's internal class hierarchy. Every internal restructuring becomes a breaking change.
266
+
267
+ ### Fluent API Design
268
+
269
+ Fluent APIs (method chaining) improve readability for configuration-heavy builders:
270
+
271
+ ```typescript
272
+ // Query builder example
273
+ export class QueryBuilder<T> {
274
+ private _where: WhereClause[] = []
275
+ private _orderBy: OrderClause[] = []
276
+ private _limit?: number
277
+
278
+ where(field: keyof T, op: Operator, value: unknown): this {
279
+ this._where.push({ field: field as string, op, value })
280
+ return this
281
+ }
282
+
283
+ orderBy(field: keyof T, direction: 'asc' | 'desc' = 'asc'): this {
284
+ this._orderBy.push({ field: field as string, direction })
285
+ return this
286
+ }
287
+
288
+ limit(n: number): this {
289
+ this._limit = n
290
+ return this
291
+ }
292
+
293
+ build(): Query<T> {
294
+ return { where: this._where, orderBy: this._orderBy, limit: this._limit }
295
+ }
296
+ }
297
+
298
+ // Consumer:
299
+ const query = new QueryBuilder<User>()
300
+ .where('active', '=', true)
301
+ .orderBy('createdAt', 'desc')
302
+ .limit(10)
303
+ .build()
304
+ ```
305
+
306
+ Use fluent APIs for builders and configuration DSLs. Avoid them for operational functions — `parseConfig(input).validate().execute()` is harder to debug than three explicit function calls.
@@ -0,0 +1,247 @@
1
+ ---
2
+ name: library-architecture
3
+ description: Module design, dependency minimization, tree-shaking enablement, and plugin patterns for published libraries
4
+ topics: [library, architecture, modules, tree-shaking, plugins, dependencies, design]
5
+ ---
6
+
7
+ Library architecture is constrained by two forces that do not apply to applications: the consumer's bundle size budget and the consumer's dependency graph. Every architectural decision must account for what the library adds to consumers who import it — not just the feature complexity it solves. A well-architected library is modular by default, has minimal or zero runtime dependencies, enables tree-shaking so consumers pay only for what they use, and provides extension points that don't require forking the library.
8
+
9
+ ## Summary
10
+
11
+ Design libraries as collections of composable, independently tree-shakeable units. The root module is a barrel export; each feature module is independently importable. Minimize runtime dependencies to zero where possible — every dependency you add becomes a transitive dependency for every consumer. Enable tree-shaking by using ES modules, avoiding side effects, and ensuring the `"sideEffects": false` field is accurate. Plugin patterns allow extension without coupling; prefer dependency injection and factory functions over class hierarchies.
12
+
13
+ Core architectural decisions:
14
+ - Module boundary: each exported function/class is its own module file, barrel at root
15
+ - Dependency strategy: zero runtime deps preferred; peer deps for framework integration
16
+ - Side effects: none at module load time; `"sideEffects": false` in package.json
17
+ - Extension pattern: plugin factory or dependency injection, not subclassing
18
+ - Error strategy: typed error classes, not string codes; never swallow errors
19
+
20
+ ## Deep Guidance
21
+
22
+ ### Module Boundary Design
23
+
24
+ Each public export should have a clear, single responsibility. The module structure mirrors the API surface:
25
+
26
+ ```
27
+ src/
28
+ ├── index.ts # Barrel: re-exports all public APIs
29
+ ├── parser.ts # parseConfig, ParseOptions, ParseError
30
+ ├── validator.ts # validateSchema, ValidationResult, ValidationError
31
+ ├── client.ts # createClient, ClientOptions, Client interface
32
+ ├── types.ts # Shared types used by multiple modules
33
+ ├── errors.ts # Base error classes
34
+ └── internal/
35
+ ├── cache.ts # Internal LRU cache (not exported)
36
+ ├── http.ts # Internal HTTP utilities
37
+ └── utils.ts # Internal pure utilities
38
+ ```
39
+
40
+ **The key constraint:** modules in `internal/` must never be imported by consumers. Enforce this with an ESLint rule:
41
+
42
+ ```json
43
+ // .eslintrc.json
44
+ {
45
+ "rules": {
46
+ "no-restricted-imports": ["error", {
47
+ "patterns": ["*/internal/*"]
48
+ }]
49
+ }
50
+ }
51
+ ```
52
+
53
+ This rule is for consumer code, not for library internals. The library's own modules can freely import from `internal/`.
54
+
55
+ ### Dependency Minimization
56
+
57
+ Every runtime dependency you add has costs:
58
+ 1. Adds to install size for all consumers
59
+ 2. Creates a version conflict risk (consumer uses different version of same dep)
60
+ 3. Introduces a supply chain attack surface
61
+ 4. Creates license compliance requirements for consumers
62
+
63
+ **Target: zero runtime dependencies for utility libraries.** If the library's value is transforming data, parsing strings, or providing utilities, it should have no runtime dependencies.
64
+
65
+ **When dependencies are justified:**
66
+ - The dependency solves a genuinely hard problem with no reasonable alternative (cryptography, date parsing)
67
+ - The dependency is a peer dependency the consumer already has (React, Vue, Node.js built-ins)
68
+ - The functionality would require thousands of lines of well-tested code to replicate
69
+
70
+ **Inlining vs. depending:**
71
+ For small, stable utilities (10-50 lines), consider inlining instead of depending:
72
+ ```typescript
73
+ // Instead of: import { clamp } from 'lodash-es'
74
+ // Inline it:
75
+ function clamp(value: number, min: number, max: number): number {
76
+ return Math.min(Math.max(value, min), max)
77
+ }
78
+ ```
79
+
80
+ For large, complex utilities (crypto, parsing), depend on the established library.
81
+
82
+ ### Tree-Shaking Enablement
83
+
84
+ Tree-shaking requires the bundler to statically analyze which exports are used. Three requirements:
85
+
86
+ **1. ES module syntax throughout:**
87
+ ```typescript
88
+ // Good — static, analyzable
89
+ export function parseConfig(input: string): Config { ... }
90
+ export { validateSchema } from './validator'
91
+
92
+ // Bad — dynamic, blocks tree-shaking
93
+ module.exports = { parseConfig } // CommonJS
94
+ exports[functionName] = fn // Dynamic export key
95
+ ```
96
+
97
+ **2. No side effects at module load time:**
98
+ ```typescript
99
+ // BAD: side effect on import — breaks tree-shaking
100
+ const cache = new Map()
101
+ globalThis.__myLibCache = cache // Pollutes global on import
102
+ console.log('my-library loaded') // Log on import
103
+
104
+ // GOOD: factory function — no side effect until called
105
+ export function createCache(): Map<string, unknown> {
106
+ return new Map()
107
+ }
108
+ ```
109
+
110
+ **3. Accurate `sideEffects` field:**
111
+ ```json
112
+ // package.json — only if genuinely no side effects
113
+ { "sideEffects": false }
114
+
115
+ // If CSS imports or polyfills have side effects:
116
+ { "sideEffects": ["*.css", "src/polyfills.js"] }
117
+ ```
118
+
119
+ **Verify tree-shaking works:**
120
+ ```bash
121
+ # Use bundle-buddy, rollup-plugin-visualizer, or bundlephobia to verify
122
+ npx bundlephobia my-library@1.0.0
123
+
124
+ # Or build a minimal consumer and check the output bundle size
125
+ # A consumer that only imports parseConfig should not include validateSchema code
126
+ ```
127
+
128
+ ### Plugin Architecture Patterns
129
+
130
+ Plugins allow consumers to extend the library without modifying it. Three patterns in increasing flexibility order:
131
+
132
+ **Pattern 1: Factory function with options (simplest):**
133
+ ```typescript
134
+ export interface ClientOptions {
135
+ transport?: Transport // Consumer provides custom transport
136
+ serializer?: Serializer
137
+ logger?: Logger
138
+ }
139
+
140
+ export function createClient(options: ClientOptions = {}): Client {
141
+ const transport = options.transport ?? defaultHttpTransport()
142
+ const serializer = options.serializer ?? jsonSerializer()
143
+ const logger = options.logger ?? noopLogger()
144
+ return new ClientImpl(transport, serializer, logger)
145
+ }
146
+
147
+ // Consumer can inject their own transport:
148
+ const client = createClient({
149
+ transport: myCustomTransport,
150
+ logger: pinoLogger
151
+ })
152
+ ```
153
+
154
+ This is dependency injection — the simplest, most testable plugin pattern.
155
+
156
+ **Pattern 2: Plugin registry (for named, composable extensions):**
157
+ ```typescript
158
+ export interface Plugin {
159
+ name: string
160
+ setup(context: PluginContext): void | Promise<void>
161
+ }
162
+
163
+ export interface PluginContext {
164
+ registerTransform(name: string, fn: TransformFn): void
165
+ registerValidator(name: string, fn: ValidatorFn): void
166
+ onParse(hook: ParseHook): void
167
+ }
168
+
169
+ export function createClient(options: { plugins?: Plugin[] } = {}): Client {
170
+ const context = createPluginContext()
171
+ for (const plugin of (options.plugins ?? [])) {
172
+ plugin.setup(context)
173
+ }
174
+ return new ClientImpl(context)
175
+ }
176
+
177
+ // Consumer uses plugins:
178
+ import { myPlugin } from 'my-library-plugin-example'
179
+ const client = createClient({ plugins: [myPlugin()] })
180
+ ```
181
+
182
+ **Pattern 3: Middleware chain (for transform pipelines):**
183
+ ```typescript
184
+ export type Middleware<T> = (value: T, next: (value: T) => T) => T
185
+
186
+ export function createPipeline<T>(middlewares: Middleware<T>[]): (value: T) => T {
187
+ return (initial: T) => {
188
+ const chain = middlewares.reduceRight<(value: T) => T>(
189
+ (next, middleware) => (value) => middleware(value, next),
190
+ (value) => value
191
+ )
192
+ return chain(initial)
193
+ }
194
+ }
195
+ ```
196
+
197
+ **What to avoid:**
198
+ - Class inheritance as extension mechanism (consumers must subclass to customize — creates tight coupling)
199
+ - Singleton registries (one global registry makes testing and isolation difficult)
200
+ - Monkey-patching as extension (fragile, hidden coupling)
201
+
202
+ ### Error Architecture
203
+
204
+ Typed errors are part of the public API contract:
205
+
206
+ ```typescript
207
+ // errors.ts — exported as part of public API
208
+ export class LibraryError extends Error {
209
+ constructor(message: string, public readonly code: ErrorCode) {
210
+ super(message)
211
+ this.name = 'LibraryError'
212
+ // Maintain proper prototype chain in transpiled environments
213
+ Object.setPrototypeOf(this, new.target.prototype)
214
+ }
215
+ }
216
+
217
+ export class ParseError extends LibraryError {
218
+ constructor(
219
+ message: string,
220
+ public readonly line: number,
221
+ public readonly column: number,
222
+ public readonly source?: string
223
+ ) {
224
+ super(message, 'PARSE_ERROR')
225
+ this.name = 'ParseError'
226
+ Object.setPrototypeOf(this, new.target.prototype)
227
+ }
228
+ }
229
+
230
+ export type ErrorCode = 'PARSE_ERROR' | 'VALIDATION_ERROR' | 'NETWORK_ERROR' | 'TIMEOUT'
231
+ ```
232
+
233
+ Never throw raw `Error` objects from library code — consumers cannot distinguish library errors from their own errors. Always throw typed errors with enough context to diagnose the problem.
234
+
235
+ ### Avoiding Circular Dependencies
236
+
237
+ Circular dependencies in libraries cause subtle initialization order bugs. Enforce absence with ESLint:
238
+
239
+ ```json
240
+ {
241
+ "rules": {
242
+ "import/no-cycle": ["error", { "maxDepth": 10 }]
243
+ }
244
+ }
245
+ ```
246
+
247
+ When you detect a circular dependency, the usual fix is extracting shared types to `types.ts` (which has no imports) rather than rearranging imports.