@verbatra/sdk 0.4.4 → 0.5.0-next.1
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/README.md +0 -1
- package/dist/index.cjs +830 -172
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +299 -62
- package/dist/index.d.ts +299 -62
- package/dist/index.js +832 -178
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ type TranslationEntry$1 = Readonly<z.infer<typeof translationEntrySchema$1>>;
|
|
|
36
36
|
* Result of comparing source placeholders against translated-output placeholders as MULTISETS
|
|
37
37
|
* (counts matter). Always returned (never thrown) for an ordinary mismatch.
|
|
38
38
|
*/
|
|
39
|
-
interface PlaceholderIntegrityResult {
|
|
39
|
+
interface PlaceholderIntegrityResult$1 {
|
|
40
40
|
/** True when nothing is missing or extra: the multisets match, regardless of order. */
|
|
41
41
|
readonly matches: boolean;
|
|
42
42
|
/** Occurrences present in source but absent from the translation, one per dropped occurrence. Sorted. */
|
|
@@ -57,6 +57,13 @@ type Tone = "formal" | "informal" | "neutral";
|
|
|
57
57
|
* derives placeholders itself.
|
|
58
58
|
*/
|
|
59
59
|
type PlaceholderExtractor = (value: string) => readonly string[];
|
|
60
|
+
/**
|
|
61
|
+
* Compares a source and translated value's placeholders directly, branch-aware, instead of
|
|
62
|
+
* independently extracting each side's flat placeholder list first. Supplied by the caller (the SDK, for
|
|
63
|
+
* a format whose adapter defines one, for example ICU plural/select) so it matches the entries' format;
|
|
64
|
+
* ai-providers never derives it itself and never parses a specific format's message syntax.
|
|
65
|
+
*/
|
|
66
|
+
type PlaceholderComparator = (source: string, translated: string) => PlaceholderIntegrityResult$1;
|
|
60
67
|
/**
|
|
61
68
|
* A batch translation request. Format- and provider-neutral: it carries no prompt,
|
|
62
69
|
* model, key, or other provider-specific field. The placeholder extractor is
|
|
@@ -75,6 +82,18 @@ interface TranslateRequest {
|
|
|
75
82
|
readonly tone?: Tone;
|
|
76
83
|
/** Mandatory placeholder extractor; the output integrity check runs against it. */
|
|
77
84
|
readonly extractPlaceholders: PlaceholderExtractor;
|
|
85
|
+
/**
|
|
86
|
+
* Optional branch-aware placeholder comparator. When present, the output integrity check uses it
|
|
87
|
+
* instead of independently extracting each side's placeholders with {@link extractPlaceholders} and
|
|
88
|
+
* diffing the flat lists. Absent for a format with no plural/select sub-message structure.
|
|
89
|
+
*/
|
|
90
|
+
readonly comparePlaceholders?: PlaceholderComparator;
|
|
91
|
+
/**
|
|
92
|
+
* Optional cancellation signal for this batch. When aborted, an in-flight provider call rejects
|
|
93
|
+
* with the abort, unwrapped, instead of a {@link ProviderError} (see `guardProviderCall`). Not a
|
|
94
|
+
* plain-data field: it is never validated by `requestDataSchema` and never sent to a provider.
|
|
95
|
+
*/
|
|
96
|
+
readonly signal?: AbortSignal;
|
|
78
97
|
}
|
|
79
98
|
/** Token usage, when the provider reports it. Absent for providers without tokens (DeepL). */
|
|
80
99
|
interface Usage {
|
|
@@ -86,7 +105,7 @@ interface TranslateResult {
|
|
|
86
105
|
/** The translated value for each requested key. */
|
|
87
106
|
readonly values: ReadonlyMap<string, string>;
|
|
88
107
|
/** The placeholder-integrity outcome for each key (source vs translated placeholder sets). */
|
|
89
|
-
readonly integrity: ReadonlyMap<string, PlaceholderIntegrityResult>;
|
|
108
|
+
readonly integrity: ReadonlyMap<string, PlaceholderIntegrityResult$1>;
|
|
90
109
|
/** Token usage when the provider reports it; absent for token-less providers. */
|
|
91
110
|
readonly usage?: Usage;
|
|
92
111
|
}
|
|
@@ -187,8 +206,9 @@ type OpenAiModel = OpenAI.ChatModel;
|
|
|
187
206
|
|
|
188
207
|
/**
|
|
189
208
|
* The provider section of the config: a discriminated union over the provider id, reusing each
|
|
190
|
-
* provider's own config schema. There is no key field anywhere in this union
|
|
191
|
-
*
|
|
209
|
+
* provider's own config schema. There is no key field anywhere in this union except
|
|
210
|
+
* openai-compatible's optional `apiKeyEnvVar`, which names an environment variable rather than carrying
|
|
211
|
+
* a key value; every provider still reads its actual key from the environment at construction.
|
|
192
212
|
*/
|
|
193
213
|
declare const providerConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
194
214
|
id: z.ZodLiteral<"anthropic">;
|
|
@@ -213,6 +233,14 @@ declare const providerConfigSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
213
233
|
options: z.ZodObject<{
|
|
214
234
|
glossaryId: z.ZodOptional<z.ZodString>;
|
|
215
235
|
}, z.core.$strict>;
|
|
236
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
237
|
+
id: z.ZodLiteral<"openai-compatible">;
|
|
238
|
+
options: z.ZodObject<{
|
|
239
|
+
baseUrl: z.ZodURL;
|
|
240
|
+
model: z.ZodString;
|
|
241
|
+
maxOutputTokens: z.ZodNumber;
|
|
242
|
+
apiKeyEnvVar: z.ZodOptional<z.ZodString>;
|
|
243
|
+
}, z.core.$strict>;
|
|
216
244
|
}, z.core.$strip>], "id">;
|
|
217
245
|
type ProviderConfig = z.infer<typeof providerConfigSchema>;
|
|
218
246
|
type ProviderId = ProviderConfig["id"];
|
|
@@ -260,8 +288,16 @@ declare const verbatraConfigSchema: z.ZodObject<{
|
|
|
260
288
|
options: z.ZodObject<{
|
|
261
289
|
glossaryId: z.ZodOptional<z.ZodString>;
|
|
262
290
|
}, z.core.$strict>;
|
|
291
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
292
|
+
id: z.ZodLiteral<"openai-compatible">;
|
|
293
|
+
options: z.ZodObject<{
|
|
294
|
+
baseUrl: z.ZodURL;
|
|
295
|
+
model: z.ZodString;
|
|
296
|
+
maxOutputTokens: z.ZodNumber;
|
|
297
|
+
apiKeyEnvVar: z.ZodOptional<z.ZodString>;
|
|
298
|
+
}, z.core.$strict>;
|
|
263
299
|
}, z.core.$strip>], "id">;
|
|
264
|
-
glossary: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
300
|
+
glossary: z.ZodOptional<z.ZodUnion<readonly [z.ZodRecord<z.ZodString, z.ZodString>, z.ZodString]>>;
|
|
265
301
|
tone: z.ZodOptional<z.ZodEnum<{
|
|
266
302
|
formal: "formal";
|
|
267
303
|
informal: "informal";
|
|
@@ -271,7 +307,22 @@ declare const verbatraConfigSchema: z.ZodObject<{
|
|
|
271
307
|
generatePlurals: z.ZodOptional<z.ZodBoolean>;
|
|
272
308
|
maxBatchSize: z.ZodOptional<z.ZodNumber>;
|
|
273
309
|
}, z.core.$strict>;
|
|
274
|
-
|
|
310
|
+
/**
|
|
311
|
+
* The as-authored (or as-parsed) shape of the verbatra configuration, straight from the schema: `glossary`
|
|
312
|
+
* is still the union of an inline record or a file path. {@link defineConfig} and the authoring types are
|
|
313
|
+
* built on this; `loadConfig` accepts it as input and produces the resolved {@link VerbatraConfig}.
|
|
314
|
+
*/
|
|
315
|
+
type VerbatraConfigInput = z.infer<typeof verbatraConfigSchema>;
|
|
316
|
+
/**
|
|
317
|
+
* The verbatra configuration after `loadConfig` has resolved it: identical to {@link VerbatraConfigInput}
|
|
318
|
+
* except `glossary`, which is always a plain record here. A glossary given as a file path is read,
|
|
319
|
+
* parsed, and validated by the loader before a `VerbatraConfig` is produced, so every downstream
|
|
320
|
+
* consumer (the translation flow, `watch`, the CLI) keeps receiving the same resolved shape it always
|
|
321
|
+
* did.
|
|
322
|
+
*/
|
|
323
|
+
type VerbatraConfig = Omit<VerbatraConfigInput, "glossary"> & {
|
|
324
|
+
glossary?: Readonly<Record<string, string>>;
|
|
325
|
+
};
|
|
275
326
|
|
|
276
327
|
type KnownModels<M extends string> = M extends string ? (string extends M ? never : M) : never;
|
|
277
328
|
type AuthoringVariant<Id extends ProviderId, M extends string> = Extract<ProviderConfig, {
|
|
@@ -292,19 +343,24 @@ type AuthoringProviderVariant = {
|
|
|
292
343
|
deepl: Extract<ProviderConfig, {
|
|
293
344
|
id: "deepl";
|
|
294
345
|
}>;
|
|
346
|
+
"openai-compatible": Extract<ProviderConfig, {
|
|
347
|
+
id: "openai-compatible";
|
|
348
|
+
}>;
|
|
295
349
|
};
|
|
296
350
|
/**
|
|
297
|
-
* The authoring view of the whole config for a provider id: a {@link
|
|
298
|
-
* is that id's single authoring variant, so `options.model` offers only that provider's
|
|
299
|
-
* `
|
|
300
|
-
*
|
|
351
|
+
* The authoring view of the whole config for a provider id: a {@link VerbatraConfigInput} whose
|
|
352
|
+
* `provider` is that id's single authoring variant, so `options.model` offers only that provider's
|
|
353
|
+
* models. `glossary` stays the as-authored union (inline record or file path); resolution happens only
|
|
354
|
+
* in `loadConfig`. When `TId` defaults to `ProviderId`, `provider` is the full authoring union. Every
|
|
355
|
+
* value here is assignable to {@link VerbatraConfigInput}, since a model literal is a subtype of
|
|
356
|
+
* `string`.
|
|
301
357
|
*/
|
|
302
|
-
type AuthoringConfigFor<TId extends ProviderId = ProviderId> = Omit<
|
|
358
|
+
type AuthoringConfigFor<TId extends ProviderId = ProviderId> = Omit<VerbatraConfigInput, "provider"> & {
|
|
303
359
|
provider: AuthoringProviderVariant[TId];
|
|
304
360
|
};
|
|
305
361
|
/**
|
|
306
362
|
* The authoring view of the whole config across every provider (the `TId = ProviderId`
|
|
307
|
-
* case of {@link AuthoringConfigFor}): identical to {@link
|
|
363
|
+
* case of {@link AuthoringConfigFor}): identical to {@link VerbatraConfigInput} except that
|
|
308
364
|
* `provider` offers per-provider model completions.
|
|
309
365
|
*/
|
|
310
366
|
type AuthoringConfig = AuthoringConfigFor;
|
|
@@ -316,16 +372,66 @@ type AuthoringConfig = AuthoringConfigFor;
|
|
|
316
372
|
*
|
|
317
373
|
* The model restriction is a static authoring constraint only: `loadConfig` validates `model` as a
|
|
318
374
|
* non-empty string, so a model the installed provider SDK does not yet list is flagged in the editor
|
|
319
|
-
* but still runs.
|
|
375
|
+
* but still runs. Likewise, `glossary` accepts either an inline record or a file path here; `loadConfig`
|
|
376
|
+
* resolves a path to a record before the translation flow ever sees it.
|
|
320
377
|
*
|
|
321
378
|
* @param config - The verbatra configuration object.
|
|
322
|
-
* @returns The same config, typed as {@link
|
|
379
|
+
* @returns The same config, typed as {@link VerbatraConfigInput}.
|
|
380
|
+
*/
|
|
381
|
+
declare function defineConfig(config: AuthoringConfigFor<"anthropic">): VerbatraConfigInput;
|
|
382
|
+
declare function defineConfig(config: AuthoringConfigFor<"openai">): VerbatraConfigInput;
|
|
383
|
+
declare function defineConfig(config: AuthoringConfigFor<"gemini">): VerbatraConfigInput;
|
|
384
|
+
declare function defineConfig(config: AuthoringConfigFor<"deepl">): VerbatraConfigInput;
|
|
385
|
+
declare function defineConfig(config: AuthoringConfig): VerbatraConfigInput;
|
|
386
|
+
|
|
387
|
+
/** Outcome of a bounded read: the content, or why it could not be read in bounds. */
|
|
388
|
+
type BoundedFileRead = {
|
|
389
|
+
readonly kind: "ok";
|
|
390
|
+
readonly content: string;
|
|
391
|
+
} | {
|
|
392
|
+
readonly kind: "missing";
|
|
393
|
+
} | {
|
|
394
|
+
readonly kind: "too-large";
|
|
395
|
+
};
|
|
396
|
+
/** Outcome of a bounded binary read: the bytes, or why they could not be read in bounds. */
|
|
397
|
+
type BoundedBytesRead = {
|
|
398
|
+
readonly kind: "ok";
|
|
399
|
+
readonly bytes: Uint8Array;
|
|
400
|
+
} | {
|
|
401
|
+
readonly kind: "missing";
|
|
402
|
+
} | {
|
|
403
|
+
readonly kind: "too-large";
|
|
404
|
+
};
|
|
405
|
+
/**
|
|
406
|
+
* The minimal file-system surface the SDK needs. Reads are bounded and writes are atomic. Injectable so
|
|
407
|
+
* tests stay deterministic; the format adapters do their own file IO and bypass this seam.
|
|
323
408
|
*/
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
409
|
+
interface SdkFs {
|
|
410
|
+
/** Whether a readable file exists at the path. */
|
|
411
|
+
fileExists(path: string): Promise<boolean>;
|
|
412
|
+
/**
|
|
413
|
+
* Read a file as UTF-8 through a single handle, bounded to maxBytes. TOCTOU-safe: the handle is
|
|
414
|
+
* fstat'd and the read never advances past the sized length, so swapping in a larger file cannot
|
|
415
|
+
* bypass the cap. A missing or unreadable path is "missing"; a file over the cap is "too-large".
|
|
416
|
+
*/
|
|
417
|
+
readFileBounded(path: string, maxBytes: number): Promise<BoundedFileRead>;
|
|
418
|
+
/** Read a file as raw bytes with the same TOCTOU-safe, bounded discipline as {@link readFileBounded}. */
|
|
419
|
+
readBytesBounded(path: string, maxBytes: number): Promise<BoundedBytesRead>;
|
|
420
|
+
/** Write atomically: a temp file in the same directory, then rename over the target. */
|
|
421
|
+
writeFile(path: string, data: string): Promise<void>;
|
|
422
|
+
/** Write raw bytes atomically (temp file, then rename over the target). Used for the workbook. */
|
|
423
|
+
writeBytes(path: string, data: Uint8Array): Promise<void>;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/** Where a config's resolved glossary came from, recorded for later Studio consumption. */
|
|
427
|
+
type GlossaryProvenance = {
|
|
428
|
+
readonly source: "none";
|
|
429
|
+
} | {
|
|
430
|
+
readonly source: "inline";
|
|
431
|
+
} | {
|
|
432
|
+
readonly source: "file";
|
|
433
|
+
readonly path: string;
|
|
434
|
+
};
|
|
329
435
|
|
|
330
436
|
interface LoadConfigOptions {
|
|
331
437
|
/** Directory to start the search from. Defaults to the current working directory. */
|
|
@@ -342,7 +448,42 @@ interface LoadConfigOptions {
|
|
|
342
448
|
* search, but `configOverride` takes precedence over it.
|
|
343
449
|
*/
|
|
344
450
|
readonly configPath?: string;
|
|
451
|
+
/** The file-system seam a glossary file path is read through. Defaults to {@link defaultFs}. */
|
|
452
|
+
readonly fs?: SdkFs;
|
|
345
453
|
}
|
|
454
|
+
/** Where the loaded config came from: an in-memory override, one explicit file, or a search result. */
|
|
455
|
+
type ConfigSource = {
|
|
456
|
+
readonly kind: "search" | "explicit";
|
|
457
|
+
readonly filepath: string;
|
|
458
|
+
} | {
|
|
459
|
+
readonly kind: "override";
|
|
460
|
+
};
|
|
461
|
+
/**
|
|
462
|
+
* The result of {@link loadConfigWithMeta}: the resolved config, where it was loaded from, and where its
|
|
463
|
+
* glossary came from. `filepath` (when present) and a `glossary` file path are always absolute.
|
|
464
|
+
*/
|
|
465
|
+
interface LoadedConfig {
|
|
466
|
+
readonly config: VerbatraConfig;
|
|
467
|
+
readonly source: ConfigSource;
|
|
468
|
+
readonly glossary: GlossaryProvenance;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Load and validate the verbatra configuration, returning provenance alongside it: which source it was
|
|
472
|
+
* loaded from ({@link ConfigSource}), and whether its glossary is absent, inline, or resolved from a
|
|
473
|
+
* file ({@link GlossaryProvenance}). {@link loadConfig} is a thin wrapper that returns only the
|
|
474
|
+
* `config` field; use this directly when the provenance is needed (for example, to display it or to
|
|
475
|
+
* re-resolve a glossary file relative to the same base directory).
|
|
476
|
+
*
|
|
477
|
+
* @param options - Where/what to load: `cwd`, an in-memory `configOverride`, an explicit `configPath`,
|
|
478
|
+
* or a `fs` seam override.
|
|
479
|
+
* @returns The resolved {@link VerbatraConfig}, its {@link ConfigSource}, and its {@link GlossaryProvenance}.
|
|
480
|
+
* @throws {@link SdkError} `CONFIG_NOT_FOUND`: no config was found by search, or the explicit `configPath`
|
|
481
|
+
* does not exist.
|
|
482
|
+
* @throws {@link SdkError} `CONFIG_INVALID`: a config was found but is unparseable or fails validation, or
|
|
483
|
+
* its glossary file path could not be read as a valid, UTF-8, flat string record (a raw error never
|
|
484
|
+
* escapes).
|
|
485
|
+
*/
|
|
486
|
+
declare function loadConfigWithMeta(options?: LoadConfigOptions): Promise<LoadedConfig>;
|
|
346
487
|
/**
|
|
347
488
|
* Load and validate the verbatra configuration. Supports a code-defined
|
|
348
489
|
* verbatra.config.ts and file-based configs (.verbatrarc.json/.yaml, package.json
|
|
@@ -352,12 +493,17 @@ interface LoadConfigOptions {
|
|
|
352
493
|
*
|
|
353
494
|
* Precedence: `configOverride` (validate in-memory) > `configPath` (load one explicit file) > search.
|
|
354
495
|
*
|
|
496
|
+
* A `glossary` given as a file path is read, parsed, and validated at load time (see
|
|
497
|
+
* {@link loadConfigWithMeta} for its provenance); a relative path resolves against the loaded config
|
|
498
|
+
* file's directory, or against `cwd` for a `configOverride`. Watch mode does not re-resolve a glossary
|
|
499
|
+
* file on later edits; restart to pick up a change.
|
|
500
|
+
*
|
|
355
501
|
* @param options - Where/what to load: `cwd`, an in-memory `configOverride`, or an explicit `configPath`.
|
|
356
502
|
* @returns The validated {@link VerbatraConfig}.
|
|
357
503
|
* @throws {@link SdkError} `CONFIG_NOT_FOUND`: no config was found by search, or the explicit `configPath`
|
|
358
504
|
* does not exist.
|
|
359
|
-
* @throws {@link SdkError} `CONFIG_INVALID`: a config was found but is unparseable or fails validation
|
|
360
|
-
* (a raw
|
|
505
|
+
* @throws {@link SdkError} `CONFIG_INVALID`: a config was found but is unparseable or fails validation, or
|
|
506
|
+
* its glossary file path could not be resolved (a raw error never escapes).
|
|
361
507
|
* @example
|
|
362
508
|
* ```ts
|
|
363
509
|
* import { loadConfig, translate } from "@verbatra/sdk";
|
|
@@ -388,7 +534,8 @@ declare function loadConfig(options?: LoadConfigOptions): Promise<VerbatraConfig
|
|
|
388
534
|
* - `SOURCE_UNREADABLE`: the source locale file is absent (thrown by `translate`, and by `watch` at startup).
|
|
389
535
|
* - `SOURCE_INVALID`: the source locale file could not be read or parsed; wraps the adapter read error
|
|
390
536
|
* (thrown by `translate`).
|
|
391
|
-
* - `LOCK_FILE_INVALID`: the lock-file is present but corrupt or
|
|
537
|
+
* - `LOCK_FILE_INVALID`: the lock-file is present but corrupt, oversized, or at an unsupported
|
|
538
|
+
* version (thrown by `translate`, `check`, `diff`, `exportWorkbook`, and `importWorkbook`).
|
|
392
539
|
* - `LOCALE_FAILED` (NOT thrown): the fallback `code` recorded on a failed `LocaleSummary` when a
|
|
393
540
|
* per-locale failure carries no string code of its own. See the surfaced-not-thrown distinction on
|
|
394
541
|
* `translate`.
|
|
@@ -445,6 +592,21 @@ interface LocaleResource {
|
|
|
445
592
|
readonly entries: ReadonlyMap<string, TranslationEntry>;
|
|
446
593
|
}
|
|
447
594
|
|
|
595
|
+
/**
|
|
596
|
+
* Result of comparing source placeholders against translated-output placeholders as MULTISETS
|
|
597
|
+
* (counts matter). Always returned (never thrown) for an ordinary mismatch.
|
|
598
|
+
*/
|
|
599
|
+
interface PlaceholderIntegrityResult {
|
|
600
|
+
/** True when nothing is missing or extra: the multisets match, regardless of order. */
|
|
601
|
+
readonly matches: boolean;
|
|
602
|
+
/** Occurrences present in source but absent from the translation, one per dropped occurrence. Sorted. */
|
|
603
|
+
readonly missing: readonly string[];
|
|
604
|
+
/** Occurrences present in the translation but absent from source, one per surplus occurrence. Sorted. */
|
|
605
|
+
readonly extra: readonly string[];
|
|
606
|
+
/** Same multiset present in both, but in a different order. */
|
|
607
|
+
readonly reordered: boolean;
|
|
608
|
+
}
|
|
609
|
+
|
|
448
610
|
/**
|
|
449
611
|
* The result of reading a file into core's intermediate representation.
|
|
450
612
|
*/
|
|
@@ -521,6 +683,18 @@ interface FormatAdapter {
|
|
|
521
683
|
* unparseable value returns false.
|
|
522
684
|
*/
|
|
523
685
|
validateMessage(value: string): boolean;
|
|
686
|
+
/**
|
|
687
|
+
* Optional branch-aware placeholder comparison for formats with plural/select sub-message structure.
|
|
688
|
+
* When present, callers use it instead of independently extracting each side's placeholders with
|
|
689
|
+
* {@link extractPlaceholders} and diffing the flat lists, since flattening a plural/select value loses
|
|
690
|
+
* which branch a placeholder came from. Absent for every non-ICU adapter, which is compared via
|
|
691
|
+
* `extractPlaceholders` plus `checkPlaceholders` as before.
|
|
692
|
+
*
|
|
693
|
+
* @param sourceValue - The source value.
|
|
694
|
+
* @param targetValue - The translated value to check against it.
|
|
695
|
+
* @returns The merged placeholder-integrity result across every branch. Does not throw.
|
|
696
|
+
*/
|
|
697
|
+
comparePlaceholders?(sourceValue: string, targetValue: string): PlaceholderIntegrityResult;
|
|
524
698
|
}
|
|
525
699
|
|
|
526
700
|
/** Outcome of resolving an adapter for a file. Structured; never throws. */
|
|
@@ -570,45 +744,6 @@ declare class AdapterRegistry {
|
|
|
570
744
|
resolve(filePath: string, options?: ResolveOptions): AdapterResolution;
|
|
571
745
|
}
|
|
572
746
|
|
|
573
|
-
/** Outcome of a bounded read: the content, or why it could not be read in bounds. */
|
|
574
|
-
type BoundedFileRead = {
|
|
575
|
-
readonly kind: "ok";
|
|
576
|
-
readonly content: string;
|
|
577
|
-
} | {
|
|
578
|
-
readonly kind: "missing";
|
|
579
|
-
} | {
|
|
580
|
-
readonly kind: "too-large";
|
|
581
|
-
};
|
|
582
|
-
/** Outcome of a bounded binary read: the bytes, or why they could not be read in bounds. */
|
|
583
|
-
type BoundedBytesRead = {
|
|
584
|
-
readonly kind: "ok";
|
|
585
|
-
readonly bytes: Uint8Array;
|
|
586
|
-
} | {
|
|
587
|
-
readonly kind: "missing";
|
|
588
|
-
} | {
|
|
589
|
-
readonly kind: "too-large";
|
|
590
|
-
};
|
|
591
|
-
/**
|
|
592
|
-
* The minimal file-system surface the SDK needs. Reads are bounded and writes are atomic. Injectable so
|
|
593
|
-
* tests stay deterministic; the format adapters do their own file IO and bypass this seam.
|
|
594
|
-
*/
|
|
595
|
-
interface SdkFs {
|
|
596
|
-
/** Whether a readable file exists at the path. */
|
|
597
|
-
fileExists(path: string): Promise<boolean>;
|
|
598
|
-
/**
|
|
599
|
-
* Read a file as UTF-8 through a single handle, bounded to maxBytes. TOCTOU-safe: the handle is
|
|
600
|
-
* fstat'd and the read never advances past the sized length, so swapping in a larger file cannot
|
|
601
|
-
* bypass the cap. A missing or unreadable path is "missing"; a file over the cap is "too-large".
|
|
602
|
-
*/
|
|
603
|
-
readFileBounded(path: string, maxBytes: number): Promise<BoundedFileRead>;
|
|
604
|
-
/** Read a file as raw bytes with the same TOCTOU-safe, bounded discipline as {@link readFileBounded}. */
|
|
605
|
-
readBytesBounded(path: string, maxBytes: number): Promise<BoundedBytesRead>;
|
|
606
|
-
/** Write atomically: a temp file in the same directory, then rename over the target. */
|
|
607
|
-
writeFile(path: string, data: string): Promise<void>;
|
|
608
|
-
/** Write raw bytes atomically (temp file, then rename over the target). Used for the workbook. */
|
|
609
|
-
writeBytes(path: string, data: Uint8Array): Promise<void>;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
747
|
/** Per-locale drift status: counts only, no key lists. */
|
|
613
748
|
interface LocaleCheckSummary {
|
|
614
749
|
/** The target locale this entry reports on. */
|
|
@@ -708,6 +843,61 @@ interface DiffDeps {
|
|
|
708
843
|
*/
|
|
709
844
|
declare function diff(input: DiffInput, deps?: DiffDeps): Promise<DiffSummary>;
|
|
710
845
|
|
|
846
|
+
/** Per-locale key count and drift against the source and target files, from the recorded lock baseline. */
|
|
847
|
+
interface LockLocaleState {
|
|
848
|
+
/** The target locale this entry reports on. */
|
|
849
|
+
readonly locale: string;
|
|
850
|
+
/** Number of keys the lock records a baseline for, in this locale. */
|
|
851
|
+
readonly keyCount: number;
|
|
852
|
+
/** Keys present in source but absent from the target. */
|
|
853
|
+
readonly missing: number;
|
|
854
|
+
/** Keys whose source changed since the target was last translated. */
|
|
855
|
+
readonly stale: number;
|
|
856
|
+
/** Keys whose recorded baseline still matches the source. */
|
|
857
|
+
readonly upToDate: number;
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* The lock-file's existence, version, and per-locale drift. `exists` is the outcome of an explicit
|
|
861
|
+
* probe of the lock-file path, never inferred from {@link readLockFile}'s missing-file degrade, so
|
|
862
|
+
* "no lock-file yet" and "an empty but present lock-file" stay distinguishable.
|
|
863
|
+
*/
|
|
864
|
+
type LockStateResult = {
|
|
865
|
+
readonly exists: false;
|
|
866
|
+
} | {
|
|
867
|
+
readonly exists: true;
|
|
868
|
+
readonly version: number;
|
|
869
|
+
readonly locales: readonly LockLocaleState[];
|
|
870
|
+
};
|
|
871
|
+
/** Input for {@link lockState}: the validated config and which locales to report on. */
|
|
872
|
+
interface LockStateInput {
|
|
873
|
+
/** The validated configuration (typically from {@link loadConfig}). */
|
|
874
|
+
readonly config: VerbatraConfig;
|
|
875
|
+
/** Directory the file pattern and lock-file resolve against; defaults to cwd. */
|
|
876
|
+
readonly cwd?: string;
|
|
877
|
+
/** Subset of target locales to report on; defaults to all configured target locales. */
|
|
878
|
+
readonly locales?: readonly string[];
|
|
879
|
+
}
|
|
880
|
+
/** Composition seam for {@link lockState}: inject a registry and a file system for tests. */
|
|
881
|
+
interface LockStateDeps {
|
|
882
|
+
readonly adapterRegistry?: AdapterRegistry;
|
|
883
|
+
readonly fs?: SdkFs;
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Report the lock-file's existence, version, and per-locale drift (key count from the recorded
|
|
887
|
+
* baseline, plus missing, stale, and up-to-date counts against the current source and target),
|
|
888
|
+
* without calling a provider, writing any file, or mutating the lock. `exists` comes from an
|
|
889
|
+
* explicit probe of the lock-file path: when it is absent, the result is `{ exists: false }` and
|
|
890
|
+
* no source or target file is read, since there is no recorded baseline to report drift against.
|
|
891
|
+
*
|
|
892
|
+
* @param input - The validated config and which locales to report on.
|
|
893
|
+
* @param deps - Optional composition seams (registry, file system) for tests.
|
|
894
|
+
* @returns The lock-file's existence and, when present, its version and per-locale drift.
|
|
895
|
+
* @throws {@link SdkError} `UNKNOWN_LOCALE` when a requested locale is not among the configured
|
|
896
|
+
* target locales; when the lock-file exists, also `UNKNOWN_FORMAT`, `SOURCE_UNREADABLE`,
|
|
897
|
+
* `SOURCE_INVALID`, or `LOCK_FILE_INVALID` with the same meanings as in `translate`.
|
|
898
|
+
*/
|
|
899
|
+
declare function lockState(input: LockStateInput, deps?: LockStateDeps): Promise<LockStateResult>;
|
|
900
|
+
|
|
711
901
|
/** A stable code for an SDK-originated notice (not a provider notice). */
|
|
712
902
|
type SdkNoticeCode = "PLURAL_CATEGORIES_INCOMPLETE" | "SUB_BATCH_FAILED" | "BLANK_ROW_BASELINE_RETAINED";
|
|
713
903
|
/**
|
|
@@ -956,6 +1146,53 @@ interface ImportWorkbookDeps {
|
|
|
956
1146
|
*/
|
|
957
1147
|
declare function importWorkbook(input: ImportWorkbookInput, deps?: ImportWorkbookDeps): Promise<RunSummary>;
|
|
958
1148
|
|
|
1149
|
+
/**
|
|
1150
|
+
* The lock-file shape: for each target locale, a map of key to the source content hash from which that
|
|
1151
|
+
* target key was last translated. The baseline core's diff consumes to detect changed source strings.
|
|
1152
|
+
*/
|
|
1153
|
+
interface LockFile {
|
|
1154
|
+
readonly version: number;
|
|
1155
|
+
readonly locales: Readonly<Record<string, Readonly<Record<string, string>>>>;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
/** Input for {@link loadLockFile}: the directory the lock-file resolves against. */
|
|
1159
|
+
interface LoadLockFileInput {
|
|
1160
|
+
/** Directory the lock-file resolves against; defaults to cwd. */
|
|
1161
|
+
readonly cwd?: string;
|
|
1162
|
+
}
|
|
1163
|
+
/** Composition seam for {@link loadLockFile}: inject a file system for tests. */
|
|
1164
|
+
interface LoadLockFileDeps {
|
|
1165
|
+
readonly fs?: SdkFs;
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Read the project's lock-file: a thin public wrapper around the internal lock-file reader,
|
|
1169
|
+
* following the same input/deps convention as {@link check}, {@link diff}, and {@link lockState}.
|
|
1170
|
+
* A missing lock-file degrades to an empty lock (first-run), the same behavior `translate` and
|
|
1171
|
+
* `watch` rely on internally. Callers that need to distinguish "no lock-file yet" from "an empty
|
|
1172
|
+
* but present lock-file" should use {@link lockState} instead, which probes the file's existence
|
|
1173
|
+
* explicitly.
|
|
1174
|
+
*
|
|
1175
|
+
* @param input - The directory the lock-file resolves against.
|
|
1176
|
+
* @param deps - Optional composition seam (file system) for tests.
|
|
1177
|
+
* @returns The parsed lock-file, or an empty lock when none exists yet.
|
|
1178
|
+
* @throws {@link SdkError} `LOCK_FILE_INVALID` when the lock-file is present but corrupt, oversized,
|
|
1179
|
+
* or at an unsupported version.
|
|
1180
|
+
*/
|
|
1181
|
+
declare function loadLockFile(input?: LoadLockFileInput, deps?: LoadLockFileDeps): Promise<LockFile>;
|
|
1182
|
+
|
|
1183
|
+
/** The committed lock-file name, chosen to be obviously JSON and to not match a `*.lock` ignore rule. */
|
|
1184
|
+
declare const LOCK_FILE_NAME = "verbatra.lock.json";
|
|
1185
|
+
|
|
1186
|
+
/**
|
|
1187
|
+
* The subset of {@link ProviderId} that `init` can scaffold: every provider with a single required
|
|
1188
|
+
* environment variable. "openai-compatible" is deliberately excluded: unlike every other provider it
|
|
1189
|
+
* has no single required environment variable, only a three-tier
|
|
1190
|
+
* apiKeyEnvVar/OPENAI_COMPATIBLE_API_KEY/placeholder fallback (see resolveOpenAiCompatibleKey in
|
|
1191
|
+
* @verbatra/ai-providers), so it does not fit the `providerEnv` table below and `init` scaffolding does
|
|
1192
|
+
* not offer it. This is the single source of truth for the exclusion; other packages import this type
|
|
1193
|
+
* rather than re-deriving `Exclude<ProviderId, "openai-compatible">` themselves.
|
|
1194
|
+
*/
|
|
1195
|
+
type ScaffoldableProviderId = Exclude<ProviderId, "openai-compatible">;
|
|
959
1196
|
/**
|
|
960
1197
|
* Read-only metadata the CLI `init` scaffold derives its tables from, so the CLI never restates the
|
|
961
1198
|
* provider, env-var, model, or format truth owned by core and ai-providers.
|
|
@@ -1070,4 +1307,4 @@ interface WatchController {
|
|
|
1070
1307
|
*/
|
|
1071
1308
|
declare function watch(input: WatchInput, deps?: WatchDeps): Promise<WatchController>;
|
|
1072
1309
|
|
|
1073
|
-
export { type CheckDeps, type CheckInput, type CheckSummary, type CreateProvider, type CreateWatcher, DEFAULT_WORKBOOK_PATH, type DiffDeps, type DiffInput, type DiffSummary, type ExportWorkbookDeps, type ExportWorkbookInput, type ExportWorkbookResult, type ImportWorkbookDeps, type ImportWorkbookInput, type LoadConfigOptions, type LocaleCheckSummary, type LocaleDiff, type LocaleNotice, type LocaleSummary, type ProviderConfig, type ProviderId, type RunSummary, type RunTranslate, SdkError, type SdkErrorCode, type SdkFs, type SdkNotice, type SdkNoticeCode, type SupportedFormat$1 as SupportedFormat, type TranslateDeps, type TranslateInput, type VerbatraConfig, type WatchController, type WatchDeps, type WatchInput, type WatchRunResult, type Watcher, check, defineConfig, diff, exportWorkbook, importWorkbook, loadConfig, scaffoldingMetadata, translate, verbatraConfigSchema, watch };
|
|
1310
|
+
export { type CheckDeps, type CheckInput, type CheckSummary, type ConfigSource, type CreateProvider, type CreateWatcher, DEFAULT_WORKBOOK_PATH, type DiffDeps, type DiffInput, type DiffSummary, type ExportWorkbookDeps, type ExportWorkbookInput, type ExportWorkbookResult, type GlossaryProvenance, type ImportWorkbookDeps, type ImportWorkbookInput, LOCK_FILE_NAME, type LoadConfigOptions, type LoadLockFileDeps, type LoadLockFileInput, type LoadedConfig, type LocaleCheckSummary, type LocaleDiff, type LocaleNotice, type LocaleSummary, type LockFile, type LockLocaleState, type LockStateDeps, type LockStateInput, type LockStateResult, type ProviderConfig, type ProviderId, type RunSummary, type RunTranslate, type ScaffoldableProviderId, SdkError, type SdkErrorCode, type SdkFs, type SdkNotice, type SdkNoticeCode, type SupportedFormat$1 as SupportedFormat, type TranslateDeps, type TranslateInput, type VerbatraConfig, type VerbatraConfigInput, type WatchController, type WatchDeps, type WatchInput, type WatchRunResult, type Watcher, check, defineConfig, diff, exportWorkbook, importWorkbook, loadConfig, loadConfigWithMeta, loadLockFile, lockState, scaffoldingMetadata, translate, verbatraConfigSchema, watch };
|