@verbatra/sdk 0.1.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.
- package/LICENSE +21 -0
- package/README.md +116 -0
- package/dist/index.cjs +1805 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +414 -0
- package/dist/index.d.ts +414 -0
- package/dist/index.js +1773 -0
- package/dist/index.js.map +1 -0
- package/package.json +89 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { ProviderNotice, TranslationProvider } from '@verbatra/ai-providers';
|
|
3
|
+
import { AdapterRegistry } from '@verbatra/format-adapters';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The verbatra project configuration. Non-secret only: it carries no API key (the
|
|
7
|
+
* provider reads its key from the environment), and unknown top-level keys are rejected
|
|
8
|
+
* so a stray secret cannot hide in the config. Validated with zod at the boundary
|
|
9
|
+
* regardless of where it was loaded from.
|
|
10
|
+
*/
|
|
11
|
+
declare const verbatraConfigSchema: z.ZodObject<{
|
|
12
|
+
sourceLocale: z.ZodString;
|
|
13
|
+
targetLocales: z.ZodArray<z.ZodString>;
|
|
14
|
+
format: z.ZodEnum<{
|
|
15
|
+
"i18next-json": "i18next-json";
|
|
16
|
+
"vue-i18n-json": "vue-i18n-json";
|
|
17
|
+
"next-intl-json": "next-intl-json";
|
|
18
|
+
"ngx-translate-json": "ngx-translate-json";
|
|
19
|
+
}>;
|
|
20
|
+
files: z.ZodObject<{
|
|
21
|
+
pattern: z.ZodString;
|
|
22
|
+
}, z.core.$strict>;
|
|
23
|
+
provider: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
24
|
+
id: z.ZodLiteral<"anthropic">;
|
|
25
|
+
options: z.ZodObject<{
|
|
26
|
+
model: z.ZodString;
|
|
27
|
+
maxTokens: z.ZodNumber;
|
|
28
|
+
}, z.core.$strict>;
|
|
29
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
30
|
+
id: z.ZodLiteral<"openai">;
|
|
31
|
+
options: z.ZodObject<{
|
|
32
|
+
model: z.ZodString;
|
|
33
|
+
maxOutputTokens: z.ZodNumber;
|
|
34
|
+
}, z.core.$strict>;
|
|
35
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
36
|
+
id: z.ZodLiteral<"gemini">;
|
|
37
|
+
options: z.ZodObject<{
|
|
38
|
+
model: z.ZodString;
|
|
39
|
+
maxOutputTokens: z.ZodNumber;
|
|
40
|
+
}, z.core.$strict>;
|
|
41
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
42
|
+
id: z.ZodLiteral<"deepl">;
|
|
43
|
+
options: z.ZodObject<{
|
|
44
|
+
glossaryId: z.ZodOptional<z.ZodString>;
|
|
45
|
+
}, z.core.$strict>;
|
|
46
|
+
}, z.core.$strip>], "id">;
|
|
47
|
+
glossary: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
48
|
+
tone: z.ZodOptional<z.ZodEnum<{
|
|
49
|
+
formal: "formal";
|
|
50
|
+
informal: "informal";
|
|
51
|
+
neutral: "neutral";
|
|
52
|
+
}>>;
|
|
53
|
+
}, z.core.$strict>;
|
|
54
|
+
type VerbatraConfig = z.infer<typeof verbatraConfigSchema>;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Identity helper for authoring a code-defined verbatra.config.ts. It returns its
|
|
58
|
+
* argument unchanged; its only purpose is to give the author full type inference and
|
|
59
|
+
* editor autocomplete on the config object.
|
|
60
|
+
*/
|
|
61
|
+
declare function defineConfig(config: VerbatraConfig): VerbatraConfig;
|
|
62
|
+
|
|
63
|
+
interface LoadConfigOptions {
|
|
64
|
+
/** Directory to start the search from. Defaults to the current working directory. */
|
|
65
|
+
readonly cwd?: string;
|
|
66
|
+
/**
|
|
67
|
+
* A pre-resolved config object (e.g. one passed in code) to validate instead of
|
|
68
|
+
* searching the file system. Still validated with zod, exactly like a loaded file.
|
|
69
|
+
*/
|
|
70
|
+
readonly configOverride?: unknown;
|
|
71
|
+
/**
|
|
72
|
+
* An explicit config file to load instead of searching. A relative path resolves
|
|
73
|
+
* against `cwd` (an absolute path is used as given), then cosmiconfig's load() parses
|
|
74
|
+
* it with the same loaders search uses (.json/.yaml/.ts), and it is zod-validated at
|
|
75
|
+
* the boundary exactly like a searched file. A missing file is CONFIG_NOT_FOUND; a
|
|
76
|
+
* present-but-unparseable/invalid file is CONFIG_INVALID. Precedence: configOverride
|
|
77
|
+
* wins over configPath, which wins over search.
|
|
78
|
+
*/
|
|
79
|
+
readonly configPath?: string;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Load and validate the verbatra configuration. Supports a code-defined
|
|
83
|
+
* verbatra.config.ts and file-based configs (.verbatrarc.json/.yaml, package.json
|
|
84
|
+
* property) through cosmiconfig + cosmiconfig-typescript-loader. Multiple sources ->
|
|
85
|
+
* first-found-wins by cosmiconfig precedence. Any failure is a structured SdkError;
|
|
86
|
+
* a raw zod error is never thrown upward and an unvalidated config never proceeds.
|
|
87
|
+
*
|
|
88
|
+
* Precedence: `configOverride` (validate in-memory) > `configPath` (load one explicit file) > search.
|
|
89
|
+
*
|
|
90
|
+
* @param options - Where/what to load: `cwd`, an in-memory `configOverride`, or an explicit `configPath`.
|
|
91
|
+
* @returns The validated {@link VerbatraConfig}.
|
|
92
|
+
* @throws {@link SdkError} `CONFIG_NOT_FOUND`: no config was found by search, or the explicit `configPath`
|
|
93
|
+
* does not exist.
|
|
94
|
+
* @throws {@link SdkError} `CONFIG_INVALID`: a config was found but is unparseable or fails validation
|
|
95
|
+
* (a raw zod error never escapes).
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* import { loadConfig, translate } from "@verbatra/sdk";
|
|
99
|
+
*
|
|
100
|
+
* // Search upward from the cwd (verbatra.config.ts, .verbatrarc.json, or a package.json "verbatra" key):
|
|
101
|
+
* const config = await loadConfig();
|
|
102
|
+
* // Or load one explicit file (relative resolves against cwd; absolute as given):
|
|
103
|
+
* // const config = await loadConfig({ configPath: "verbatra.config.ts" });
|
|
104
|
+
*
|
|
105
|
+
* const summary = await translate({ config });
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
declare function loadConfig(options?: LoadConfigOptions): Promise<VerbatraConfig>;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The provider section of the config: a discriminated union over the provider id,
|
|
112
|
+
* reusing each provider's own exported config schema. There is no key field anywhere
|
|
113
|
+
* in this union. The provider reads its API key from the environment at construction.
|
|
114
|
+
*
|
|
115
|
+
* This union and the factory table below are co-located on purpose: adding a provider
|
|
116
|
+
* is a single edit here (one union variant plus one table entry), and the mapped-type
|
|
117
|
+
* table makes the two sets provably identical at compile time.
|
|
118
|
+
*/
|
|
119
|
+
declare const providerConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
120
|
+
id: z.ZodLiteral<"anthropic">;
|
|
121
|
+
options: z.ZodObject<{
|
|
122
|
+
model: z.ZodString;
|
|
123
|
+
maxTokens: z.ZodNumber;
|
|
124
|
+
}, z.core.$strict>;
|
|
125
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
126
|
+
id: z.ZodLiteral<"openai">;
|
|
127
|
+
options: z.ZodObject<{
|
|
128
|
+
model: z.ZodString;
|
|
129
|
+
maxOutputTokens: z.ZodNumber;
|
|
130
|
+
}, z.core.$strict>;
|
|
131
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
132
|
+
id: z.ZodLiteral<"gemini">;
|
|
133
|
+
options: z.ZodObject<{
|
|
134
|
+
model: z.ZodString;
|
|
135
|
+
maxOutputTokens: z.ZodNumber;
|
|
136
|
+
}, z.core.$strict>;
|
|
137
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
138
|
+
id: z.ZodLiteral<"deepl">;
|
|
139
|
+
options: z.ZodObject<{
|
|
140
|
+
glossaryId: z.ZodOptional<z.ZodString>;
|
|
141
|
+
}, z.core.$strict>;
|
|
142
|
+
}, z.core.$strip>], "id">;
|
|
143
|
+
type ProviderConfig = z.infer<typeof providerConfigSchema>;
|
|
144
|
+
type ProviderId = ProviderConfig["id"];
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Structured, secret-free error codes for the SDK boundaries. A key never appears in
|
|
148
|
+
* any message: provider/adapter/core errors are already secret-free, and the SDK never
|
|
149
|
+
* reads or holds a key. Each names a distinct boundary:
|
|
150
|
+
*
|
|
151
|
+
* - `CONFIG_NOT_FOUND`:no config was found by search, or an explicit `configPath` does not exist
|
|
152
|
+
* (thrown by `loadConfig`).
|
|
153
|
+
* - `CONFIG_INVALID`:a config was found but is unparseable or fails validation (thrown by `loadConfig`).
|
|
154
|
+
* - `UNKNOWN_FORMAT`:no adapter is registered for the configured format (thrown by `translate`).
|
|
155
|
+
* - `PROVIDER_CONSTRUCTION_FAILED`:the provider factory threw; wraps the provider's own error, including
|
|
156
|
+
* a missing `*_API_KEY` reported as `MISSING_API_KEY` (thrown by `translate`, non-dry-run only).
|
|
157
|
+
* - `SOURCE_UNREADABLE`:the source locale file is absent (thrown by `translate`, and by `watch` at startup).
|
|
158
|
+
* - `SOURCE_INVALID`:the source locale file could not be read or parsed; wraps the adapter read error
|
|
159
|
+
* (thrown by `translate`).
|
|
160
|
+
* - `LOCK_FILE_INVALID`:the lock-file is present but corrupt or oversized (thrown by `translate`).
|
|
161
|
+
* - `LOCALE_FAILED` (NOT thrown): the fallback `code` recorded on a failed `LocaleSummary` when a
|
|
162
|
+
* per-locale failure carries no string code of its own. See the surfaced-not-thrown distinction on
|
|
163
|
+
* `translate`.
|
|
164
|
+
*/
|
|
165
|
+
type SdkErrorCode = "CONFIG_NOT_FOUND" | "CONFIG_INVALID" | "UNKNOWN_FORMAT" | "PROVIDER_CONSTRUCTION_FAILED" | "SOURCE_UNREADABLE" | "SOURCE_INVALID" | "LOCK_FILE_INVALID" | "LOCALE_FAILED";
|
|
166
|
+
/** The single structured error the SDK throws or records. Never carries a secret. */
|
|
167
|
+
declare class SdkError extends Error {
|
|
168
|
+
/** The stable {@link SdkErrorCode} for this failure; branch on this, not the message. */
|
|
169
|
+
readonly code: SdkErrorCode;
|
|
170
|
+
/**
|
|
171
|
+
* @param code - The stable failure code.
|
|
172
|
+
* @param message - A fixed, secret-free message; the SDK never holds a key to put here.
|
|
173
|
+
*/
|
|
174
|
+
constructor(code: SdkErrorCode, message: string);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Structured outcome for one target locale; surfaced as data on the run, never thrown. */
|
|
178
|
+
interface LocaleSummary {
|
|
179
|
+
/** The target locale this summary is for. */
|
|
180
|
+
readonly locale: string;
|
|
181
|
+
/** Whether this locale's run succeeded or failed (a failure does not abort the run). */
|
|
182
|
+
readonly status: "succeeded" | "failed";
|
|
183
|
+
/**
|
|
184
|
+
* Keys translated and written this run. In dry-run, the keys that WOULD be translated
|
|
185
|
+
* (the provider is not called and nothing is written).
|
|
186
|
+
*/
|
|
187
|
+
readonly translated: readonly string[];
|
|
188
|
+
/** Keys already up to date, left unchanged this run. */
|
|
189
|
+
readonly unchanged: readonly string[];
|
|
190
|
+
/** Target keys with no corresponding source key (candidates for removal). */
|
|
191
|
+
readonly orphaned: readonly string[];
|
|
192
|
+
/** Source keys flagged invalid-ICU that were skipped for translation this run. */
|
|
193
|
+
readonly invalidIcuSource: readonly string[];
|
|
194
|
+
/** Translated keys that failed the placeholder-integrity check and were withheld. */
|
|
195
|
+
readonly integrityMismatches: readonly string[];
|
|
196
|
+
/** Provider notices for this locale (e.g. DeepL graceful-degradation); empty for LLM providers. */
|
|
197
|
+
readonly notices: readonly ProviderNotice[];
|
|
198
|
+
/**
|
|
199
|
+
* Present only when status is "failed": a structured, secret-free error. `code` is a PRESERVED string
|
|
200
|
+
* (the underlying provider/adapter error's `code`, or `"LOCALE_FAILED"` as a fallback), intentionally
|
|
201
|
+
* wider than {@link SdkErrorCode}, so do not treat it as a closed set.
|
|
202
|
+
*/
|
|
203
|
+
readonly error?: {
|
|
204
|
+
readonly code: string;
|
|
205
|
+
readonly message: string;
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/** The aggregate result of a run across all target locales. */
|
|
209
|
+
interface RunSummary {
|
|
210
|
+
/** Whether this was a dry-run (no provider calls, no writes). */
|
|
211
|
+
readonly dryRun: boolean;
|
|
212
|
+
/** One {@link LocaleSummary} per target locale, in config order. */
|
|
213
|
+
readonly locales: readonly LocaleSummary[];
|
|
214
|
+
/** Locales whose run succeeded. */
|
|
215
|
+
readonly succeeded: readonly string[];
|
|
216
|
+
/** Locales whose run failed (see each locale's `error`). */
|
|
217
|
+
readonly failed: readonly string[];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Outcome of a bounded read: the content, or why it could not be read in bounds. */
|
|
221
|
+
type BoundedFileRead = {
|
|
222
|
+
readonly kind: "ok";
|
|
223
|
+
readonly content: string;
|
|
224
|
+
} | {
|
|
225
|
+
readonly kind: "missing";
|
|
226
|
+
} | {
|
|
227
|
+
readonly kind: "too-large";
|
|
228
|
+
};
|
|
229
|
+
/**
|
|
230
|
+
* The minimal file-system surface the SDK needs for the lock-file and for existence
|
|
231
|
+
* checks. Injectable so tests stay deterministic; the format adapters do their own
|
|
232
|
+
* file IO and are not routed through this seam.
|
|
233
|
+
*/
|
|
234
|
+
interface SdkFs {
|
|
235
|
+
/** Whether a readable file exists at the path. */
|
|
236
|
+
fileExists(path: string): Promise<boolean>;
|
|
237
|
+
/**
|
|
238
|
+
* Read a file as UTF-8 through a single handle, bounded to maxBytes. TOCTOU-safe: the
|
|
239
|
+
* handle is fstat'd and the read never advances past the sized length, so swapping the
|
|
240
|
+
* path for a larger file after the size check cannot bypass the cap. A missing or
|
|
241
|
+
* unreadable path is "missing" (first-run); a file over the cap is "too-large".
|
|
242
|
+
*/
|
|
243
|
+
readFileBounded(path: string, maxBytes: number): Promise<BoundedFileRead>;
|
|
244
|
+
/** Write atomically: a temp file in the same directory, then rename over the target. */
|
|
245
|
+
writeFile(path: string, data: string): Promise<void>;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Builds the provider from its config. Injectable so tests stay offline. */
|
|
249
|
+
type CreateProvider = (config: ProviderConfig) => TranslationProvider;
|
|
250
|
+
|
|
251
|
+
/** Everything the one-shot run needs: the validated config and where/how to run it. */
|
|
252
|
+
interface TranslateInput {
|
|
253
|
+
/** The validated configuration (typically from {@link loadConfig}). */
|
|
254
|
+
readonly config: VerbatraConfig;
|
|
255
|
+
/** Directory the file pattern and lock-file resolve against; defaults to the current working directory. */
|
|
256
|
+
readonly cwd?: string;
|
|
257
|
+
/** When true, read + diff + report only: the provider is never constructed or called and nothing is written. */
|
|
258
|
+
readonly dryRun?: boolean;
|
|
259
|
+
}
|
|
260
|
+
/** Composition seam: inject a registry, a provider builder, and a file system for tests. */
|
|
261
|
+
interface TranslateDeps {
|
|
262
|
+
/** Adapter registry to resolve the format from; defaults to the built-in registry. */
|
|
263
|
+
readonly adapterRegistry?: AdapterRegistry;
|
|
264
|
+
/** Provider builder; defaults to constructing the configured provider (which reads its key from env). */
|
|
265
|
+
readonly createProvider?: CreateProvider;
|
|
266
|
+
/** File system for existence checks and the lock-file; defaults to the real file system. */
|
|
267
|
+
readonly fs?: SdkFs;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* The one-shot end-to-end translate flow. Whole-run failures (config already validated
|
|
271
|
+
* by the caller, unknown format, provider construction, unreadable/invalid source,
|
|
272
|
+
* corrupt lock-file) throw a structured SdkError. Per-locale failures are isolated: a
|
|
273
|
+
* failing locale is reported and the run continues; the lock-file reflects exactly the
|
|
274
|
+
* locales that succeeded. Dry-run reads + diffs + reports without constructing/calling
|
|
275
|
+
* the provider and without writing any file or the lock-file.
|
|
276
|
+
*
|
|
277
|
+
* A per-locale failure does NOT throw: it is recorded on that locale's {@link LocaleSummary} as
|
|
278
|
+
* `status: "failed"` with a secret-free `{ code, message }`, where `code` is a preserved string (the
|
|
279
|
+
* underlying provider/adapter code, or `"LOCALE_FAILED"` as a fallback), not necessarily an
|
|
280
|
+
* {@link SdkErrorCode}. DeepL notices, integrity mismatches, and invalid-ICU source keys likewise
|
|
281
|
+
* surface on each `LocaleSummary`, never as throws.
|
|
282
|
+
*
|
|
283
|
+
* @param input - The validated config and run options (cwd, dryRun).
|
|
284
|
+
* @param deps - Optional composition seams (registry, provider builder, file system) for tests.
|
|
285
|
+
* @returns A {@link RunSummary}: the per-locale {@link LocaleSummary}s and the succeeded/failed locale lists.
|
|
286
|
+
* @throws {@link SdkError} `UNKNOWN_FORMAT`: no adapter is registered for the configured format.
|
|
287
|
+
* @throws {@link SdkError} `PROVIDER_CONSTRUCTION_FAILED`: the provider factory threw (this wraps the
|
|
288
|
+
* provider's own error, including a missing `*_API_KEY` reported as `MISSING_API_KEY`); only on a
|
|
289
|
+
* non-dry-run, since dry-run never constructs the provider.
|
|
290
|
+
* @throws {@link SdkError} `SOURCE_UNREADABLE`: the source locale file does not exist.
|
|
291
|
+
* @throws {@link SdkError} `SOURCE_INVALID`: the source locale file could not be read or parsed (wraps the
|
|
292
|
+
* adapter read error).
|
|
293
|
+
* @throws {@link SdkError} `LOCK_FILE_INVALID`: the lock-file is present but corrupt or oversized.
|
|
294
|
+
* @example
|
|
295
|
+
* ```ts
|
|
296
|
+
* import { loadConfig, translate } from "@verbatra/sdk";
|
|
297
|
+
*
|
|
298
|
+
* // The provider reads its API key from the environment (e.g. ANTHROPIC_API_KEY); no key is passed here.
|
|
299
|
+
* const config = await loadConfig();
|
|
300
|
+
* const summary = await translate({ config });
|
|
301
|
+
*
|
|
302
|
+
* for (const locale of summary.locales) {
|
|
303
|
+
* if (locale.status === "failed") {
|
|
304
|
+
* // Surfaced, not thrown: code is a preserved string (LOCALE_FAILED is only the fallback).
|
|
305
|
+
* console.error(`${locale.locale}: ${locale.error?.code} ${locale.error?.message}`);
|
|
306
|
+
* } else {
|
|
307
|
+
* console.log(`${locale.locale}: ${locale.translated.length} translated, ${locale.notices.length} notices`);
|
|
308
|
+
* }
|
|
309
|
+
* }
|
|
310
|
+
*
|
|
311
|
+
* // Preview only: no provider call, no writes.
|
|
312
|
+
* const preview = await translate({ config, dryRun: true });
|
|
313
|
+
* ```
|
|
314
|
+
*/
|
|
315
|
+
declare function translate(input: TranslateInput, deps?: TranslateDeps): Promise<RunSummary>;
|
|
316
|
+
|
|
317
|
+
/** A minimal source-change event source. Production wraps chokidar; tests inject a stub. */
|
|
318
|
+
interface Watcher {
|
|
319
|
+
/** Register a listener invoked once per coalesced source-change event. */
|
|
320
|
+
onChange(listener: () => void): void;
|
|
321
|
+
/** Stop watching and release the underlying resources. */
|
|
322
|
+
close(): Promise<void>;
|
|
323
|
+
}
|
|
324
|
+
/** Builds a {@link Watcher} for the given paths; the seam production fills with chokidar. */
|
|
325
|
+
type CreateWatcher = (paths: readonly string[]) => Watcher;
|
|
326
|
+
/** The run a watch trigger performs: the slice-1 one-shot translate, unchanged. */
|
|
327
|
+
type RunTranslate = (input: TranslateInput) => Promise<RunSummary>;
|
|
328
|
+
/** The outcome of one run, surfaced to the caller; never carries a secret. */
|
|
329
|
+
type WatchRunResult = {
|
|
330
|
+
readonly status: "succeeded";
|
|
331
|
+
readonly summary: RunSummary;
|
|
332
|
+
} | {
|
|
333
|
+
readonly status: "failed";
|
|
334
|
+
/**
|
|
335
|
+
* A secret-free projection of the run's failure. `code` is a preserved string (the underlying
|
|
336
|
+
* error's `code`, or `"WATCH_RUN_FAILED"` as a fallback), not an {@link SdkErrorCode}.
|
|
337
|
+
*/
|
|
338
|
+
readonly error: {
|
|
339
|
+
readonly code: string;
|
|
340
|
+
readonly message: string;
|
|
341
|
+
};
|
|
342
|
+
};
|
|
343
|
+
/** Everything watch mode needs: the config, optional cwd/debounce, and the per-run output callback. */
|
|
344
|
+
interface WatchInput {
|
|
345
|
+
/** The validated configuration (typically from {@link loadConfig}). */
|
|
346
|
+
readonly config: VerbatraConfig;
|
|
347
|
+
/** Directory the file pattern and lock-file resolve against; defaults to the current working directory. */
|
|
348
|
+
readonly cwd?: string;
|
|
349
|
+
/** Quiet period after the last change before a run fires; defaults to 300ms. */
|
|
350
|
+
readonly debounceMs?: number;
|
|
351
|
+
/** Called once per run with its result. The SDK does no logging; this is the only output. */
|
|
352
|
+
readonly onRun: (result: WatchRunResult) => void;
|
|
353
|
+
}
|
|
354
|
+
/** Composition seam: inject the watcher and the run for deterministic, offline tests. */
|
|
355
|
+
interface WatchDeps {
|
|
356
|
+
/** Adapter registry passed through to each run; defaults to the built-in registry. */
|
|
357
|
+
readonly adapterRegistry?: AdapterRegistry;
|
|
358
|
+
/** Provider builder passed through to each run; defaults to constructing the configured provider. */
|
|
359
|
+
readonly createProvider?: CreateProvider;
|
|
360
|
+
/** File system passed through to each run; defaults to the real file system. */
|
|
361
|
+
readonly fs?: SdkFs;
|
|
362
|
+
/** Source-change event source; defaults to the chokidar-backed watcher. */
|
|
363
|
+
readonly createWatcher?: CreateWatcher;
|
|
364
|
+
/** The run a trigger performs; defaults to the one-shot {@link translate}. */
|
|
365
|
+
readonly runTranslate?: RunTranslate;
|
|
366
|
+
}
|
|
367
|
+
/** Handle returned by {@link watch} to stop it. */
|
|
368
|
+
interface WatchController {
|
|
369
|
+
/** Stop accepting triggers, close the watcher, and await the in-flight run to completion. */
|
|
370
|
+
stop(): Promise<void>;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Start watching the source file and re-run the one-shot translate on each debounced change.
|
|
374
|
+
* Watch adds no translation/diff/lock logic: it watches, debounces, serializes, and invokes the
|
|
375
|
+
* existing translate() unchanged. The state machine is IDLE <-> RUNNING with a single boolean
|
|
376
|
+
* pending-rerun flag (mid-run changes collapse into one immediate follow-up; never two runs at
|
|
377
|
+
* once). A missing source at startup is a hard error; a run that fails after start is reported and
|
|
378
|
+
* watching continues. Returns a controller whose stop() closes the watcher and awaits the in-flight
|
|
379
|
+
* run (the caller wires any signal, e.g. SIGINT, to it).
|
|
380
|
+
*
|
|
381
|
+
* Only the startup source check throws; every run outcome after start is surfaced through `onRun` as a
|
|
382
|
+
* {@link WatchRunResult} (a failed run never rejects the in-flight promise).
|
|
383
|
+
*
|
|
384
|
+
* @param input - The config, optional cwd/debounce, and the `onRun` callback that receives each result.
|
|
385
|
+
* @param deps - Optional composition seams (watcher, run, registry, provider builder, file system) for tests.
|
|
386
|
+
* @returns A {@link WatchController}; call `stop()` to close the watcher and await the in-flight run.
|
|
387
|
+
* @throws {@link SdkError} `SOURCE_UNREADABLE`: at startup only, when the source locale file is absent.
|
|
388
|
+
* @example
|
|
389
|
+
* ```ts
|
|
390
|
+
* import { loadConfig, watch } from "@verbatra/sdk";
|
|
391
|
+
*
|
|
392
|
+
* // The provider reads its API key from the environment (e.g. ANTHROPIC_API_KEY); no key is passed here.
|
|
393
|
+
* const config = await loadConfig();
|
|
394
|
+
* const controller = await watch({
|
|
395
|
+
* config,
|
|
396
|
+
* onRun: (result) => {
|
|
397
|
+
* if (result.status === "succeeded") {
|
|
398
|
+
* console.log(`ran: ${result.summary.succeeded.length} ok, ${result.summary.failed.length} failed`);
|
|
399
|
+
* } else {
|
|
400
|
+
* // Surfaced, not thrown: code is a preserved string (WATCH_RUN_FAILED is only the fallback).
|
|
401
|
+
* console.error(`run failed: ${result.error.code} ${result.error.message}`);
|
|
402
|
+
* }
|
|
403
|
+
* },
|
|
404
|
+
* });
|
|
405
|
+
*
|
|
406
|
+
* // Stop cleanly on Ctrl-C: closes the watcher and awaits the in-flight run.
|
|
407
|
+
* process.on("SIGINT", () => {
|
|
408
|
+
* void controller.stop();
|
|
409
|
+
* });
|
|
410
|
+
* ```
|
|
411
|
+
*/
|
|
412
|
+
declare function watch(input: WatchInput, deps?: WatchDeps): Promise<WatchController>;
|
|
413
|
+
|
|
414
|
+
export { type CreateProvider, type CreateWatcher, type LoadConfigOptions, type LocaleSummary, type ProviderConfig, type ProviderId, type RunSummary, type RunTranslate, SdkError, type SdkErrorCode, type SdkFs, type TranslateDeps, type TranslateInput, type VerbatraConfig, type WatchController, type WatchDeps, type WatchInput, type WatchRunResult, type Watcher, defineConfig, loadConfig, translate, verbatraConfigSchema, watch };
|