@verbatra/sdk 0.1.0 → 0.2.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/dist/index.d.cts CHANGED
@@ -226,6 +226,15 @@ type BoundedFileRead = {
226
226
  } | {
227
227
  readonly kind: "too-large";
228
228
  };
229
+ /** Outcome of a bounded binary read: the bytes, or why they could not be read in bounds. */
230
+ type BoundedBytesRead = {
231
+ readonly kind: "ok";
232
+ readonly bytes: Uint8Array;
233
+ } | {
234
+ readonly kind: "missing";
235
+ } | {
236
+ readonly kind: "too-large";
237
+ };
229
238
  /**
230
239
  * The minimal file-system surface the SDK needs for the lock-file and for existence
231
240
  * checks. Injectable so tests stay deterministic; the format adapters do their own
@@ -241,8 +250,17 @@ interface SdkFs {
241
250
  * unreadable path is "missing" (first-run); a file over the cap is "too-large".
242
251
  */
243
252
  readFileBounded(path: string, maxBytes: number): Promise<BoundedFileRead>;
253
+ /**
254
+ * Read a file as raw bytes through a single handle, bounded to maxBytes, with the SAME
255
+ * TOCTOU-safe discipline as {@link readFileBounded}: the handle is fstat'd and the read never
256
+ * advances past the sized length. Used for the untrusted workbook on import; a file over the
257
+ * cap is "too-large", a missing path is "missing".
258
+ */
259
+ readBytesBounded(path: string, maxBytes: number): Promise<BoundedBytesRead>;
244
260
  /** Write atomically: a temp file in the same directory, then rename over the target. */
245
261
  writeFile(path: string, data: string): Promise<void>;
262
+ /** Write raw bytes atomically (temp file, then rename over the target). Used for the workbook. */
263
+ writeBytes(path: string, data: Uint8Array): Promise<void>;
246
264
  }
247
265
 
248
266
  /** Builds the provider from its config. Injectable so tests stay offline. */
@@ -314,6 +332,89 @@ interface TranslateDeps {
314
332
  */
315
333
  declare function translate(input: TranslateInput, deps?: TranslateDeps): Promise<RunSummary>;
316
334
 
335
+ /** Default workbook output path, relative to the resolved working directory. */
336
+ declare const DEFAULT_WORKBOOK_PATH = "verbatra-translations.xlsx";
337
+ /** Input for {@link exportWorkbook}: the validated config and where/how to run the export. */
338
+ interface ExportWorkbookInput {
339
+ /** The validated configuration (typically from {@link loadConfig}). */
340
+ readonly config: VerbatraConfig;
341
+ /** Directory the file pattern, lock-file, and output path resolve against; defaults to cwd. */
342
+ readonly cwd?: string;
343
+ /** Output path for the workbook; defaults to {@link DEFAULT_WORKBOOK_PATH} under cwd. */
344
+ readonly out?: string;
345
+ /** Subset of target locales to export; defaults to all configured target locales. */
346
+ readonly locales?: readonly string[];
347
+ /** Include unchanged keys (off by default; export is missing-and-changed only). */
348
+ readonly includeUnchanged?: boolean;
349
+ }
350
+ /** Composition seam for {@link exportWorkbook}: inject a registry and a file system for tests. */
351
+ interface ExportWorkbookDeps {
352
+ readonly adapterRegistry?: AdapterRegistry;
353
+ readonly fs?: SdkFs;
354
+ }
355
+ /** The outcome of an export: where it was written and how many rows per locale. */
356
+ interface ExportWorkbookResult {
357
+ /** The absolute path the workbook was written to. */
358
+ readonly path: string;
359
+ /** Per-locale row counts, in config order; the same set the workbook carries. */
360
+ readonly locales: readonly {
361
+ readonly locale: string;
362
+ readonly rows: number;
363
+ }[];
364
+ }
365
+ /**
366
+ * Export the strings needing human translation into a styled `.xlsx` workbook. Reuses the same
367
+ * source read, adapter selection, and lock baseline the translate flow uses, runs `diffResources`
368
+ * per target locale to pick the rows (missing and changed by default; add unchanged with
369
+ * `includeUnchanged`), hands the neutral row model to `@verbatra/exchange`'s `buildWorkbook`, and
370
+ * writes the bytes through the {@link SdkFs} seam. No provider is called and no lock-file is written.
371
+ *
372
+ * @param input - The validated config and export options.
373
+ * @param deps - Optional composition seams (registry, file system) for tests.
374
+ * @returns Where the workbook was written and the per-locale row counts.
375
+ * @throws {@link SdkError} `UNKNOWN_FORMAT`, `SOURCE_UNREADABLE`, `SOURCE_INVALID`, `LOCK_FILE_INVALID`
376
+ * with the same meanings as in `translate`.
377
+ */
378
+ declare function exportWorkbook(input: ExportWorkbookInput, deps?: ExportWorkbookDeps): Promise<ExportWorkbookResult>;
379
+
380
+ /** Input for {@link importWorkbook}: the validated config, the workbook path, and run options. */
381
+ interface ImportWorkbookInput {
382
+ /** The validated configuration (typically from {@link loadConfig}). */
383
+ readonly config: VerbatraConfig;
384
+ /** Path to the filled workbook to import. */
385
+ readonly workbook: string;
386
+ /** Directory the file pattern, lock-file, and workbook path resolve against; defaults to cwd. */
387
+ readonly cwd?: string;
388
+ /** When true, validate and report only: write no locale file and update no lock-file. */
389
+ readonly dryRun?: boolean;
390
+ }
391
+ /** Composition seam for {@link importWorkbook}: inject a registry and a file system for tests. */
392
+ interface ImportWorkbookDeps {
393
+ readonly adapterRegistry?: AdapterRegistry;
394
+ readonly fs?: SdkFs;
395
+ }
396
+ /**
397
+ * Import a filled workbook back into the locale files. Reads the untrusted workbook through the
398
+ * SDK's bounded read, parses it with `@verbatra/exchange`'s `readWorkbook` (which bounds and
399
+ * sanitizes it), then for each target-locale data sheet runs the EXISTING core checks (source-drift
400
+ * via `contentHash`, placeholder integrity via `checkPlaceholders`, ICU via the adapter's
401
+ * `validateMessage`), writes the accepted values through the format adapter, and updates the lock
402
+ * through the existing lock logic. Returns a {@link RunSummary} structurally identical to
403
+ * `translate`'s, so the CLI formatter and exit-code rule are shared with no special case.
404
+ *
405
+ * Whole-run failures (unknown format, unreadable/invalid/oversized workbook, corrupt lock) throw a
406
+ * structured {@link SdkError}. A per-sheet failure (a locale not in config, a broken-round-trip key,
407
+ * a write failure) is isolated as that locale's `status: "failed"`, not a throw; per-row rejections
408
+ * are withheld and reported on the locale, exactly as the provider path treats integrity mismatches.
409
+ * `--dry-run` validates and reports without writing any locale or lock file.
410
+ *
411
+ * @param input - The validated config, the workbook path, and run options.
412
+ * @param deps - Optional composition seams (registry, file system) for tests.
413
+ * @returns A {@link RunSummary} with one locale per data sheet, in workbook order.
414
+ * @throws {@link SdkError} `UNKNOWN_FORMAT`, `SOURCE_UNREADABLE`, `SOURCE_INVALID`, `LOCK_FILE_INVALID`.
415
+ */
416
+ declare function importWorkbook(input: ImportWorkbookInput, deps?: ImportWorkbookDeps): Promise<RunSummary>;
417
+
317
418
  /** A minimal source-change event source. Production wraps chokidar; tests inject a stub. */
318
419
  interface Watcher {
319
420
  /** Register a listener invoked once per coalesced source-change event. */
@@ -411,4 +512,4 @@ interface WatchController {
411
512
  */
412
513
  declare function watch(input: WatchInput, deps?: WatchDeps): Promise<WatchController>;
413
514
 
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 };
515
+ export { type CreateProvider, type CreateWatcher, DEFAULT_WORKBOOK_PATH, type ExportWorkbookDeps, type ExportWorkbookInput, type ExportWorkbookResult, type ImportWorkbookDeps, type ImportWorkbookInput, 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, exportWorkbook, importWorkbook, loadConfig, translate, verbatraConfigSchema, watch };
package/dist/index.d.ts CHANGED
@@ -226,6 +226,15 @@ type BoundedFileRead = {
226
226
  } | {
227
227
  readonly kind: "too-large";
228
228
  };
229
+ /** Outcome of a bounded binary read: the bytes, or why they could not be read in bounds. */
230
+ type BoundedBytesRead = {
231
+ readonly kind: "ok";
232
+ readonly bytes: Uint8Array;
233
+ } | {
234
+ readonly kind: "missing";
235
+ } | {
236
+ readonly kind: "too-large";
237
+ };
229
238
  /**
230
239
  * The minimal file-system surface the SDK needs for the lock-file and for existence
231
240
  * checks. Injectable so tests stay deterministic; the format adapters do their own
@@ -241,8 +250,17 @@ interface SdkFs {
241
250
  * unreadable path is "missing" (first-run); a file over the cap is "too-large".
242
251
  */
243
252
  readFileBounded(path: string, maxBytes: number): Promise<BoundedFileRead>;
253
+ /**
254
+ * Read a file as raw bytes through a single handle, bounded to maxBytes, with the SAME
255
+ * TOCTOU-safe discipline as {@link readFileBounded}: the handle is fstat'd and the read never
256
+ * advances past the sized length. Used for the untrusted workbook on import; a file over the
257
+ * cap is "too-large", a missing path is "missing".
258
+ */
259
+ readBytesBounded(path: string, maxBytes: number): Promise<BoundedBytesRead>;
244
260
  /** Write atomically: a temp file in the same directory, then rename over the target. */
245
261
  writeFile(path: string, data: string): Promise<void>;
262
+ /** Write raw bytes atomically (temp file, then rename over the target). Used for the workbook. */
263
+ writeBytes(path: string, data: Uint8Array): Promise<void>;
246
264
  }
247
265
 
248
266
  /** Builds the provider from its config. Injectable so tests stay offline. */
@@ -314,6 +332,89 @@ interface TranslateDeps {
314
332
  */
315
333
  declare function translate(input: TranslateInput, deps?: TranslateDeps): Promise<RunSummary>;
316
334
 
335
+ /** Default workbook output path, relative to the resolved working directory. */
336
+ declare const DEFAULT_WORKBOOK_PATH = "verbatra-translations.xlsx";
337
+ /** Input for {@link exportWorkbook}: the validated config and where/how to run the export. */
338
+ interface ExportWorkbookInput {
339
+ /** The validated configuration (typically from {@link loadConfig}). */
340
+ readonly config: VerbatraConfig;
341
+ /** Directory the file pattern, lock-file, and output path resolve against; defaults to cwd. */
342
+ readonly cwd?: string;
343
+ /** Output path for the workbook; defaults to {@link DEFAULT_WORKBOOK_PATH} under cwd. */
344
+ readonly out?: string;
345
+ /** Subset of target locales to export; defaults to all configured target locales. */
346
+ readonly locales?: readonly string[];
347
+ /** Include unchanged keys (off by default; export is missing-and-changed only). */
348
+ readonly includeUnchanged?: boolean;
349
+ }
350
+ /** Composition seam for {@link exportWorkbook}: inject a registry and a file system for tests. */
351
+ interface ExportWorkbookDeps {
352
+ readonly adapterRegistry?: AdapterRegistry;
353
+ readonly fs?: SdkFs;
354
+ }
355
+ /** The outcome of an export: where it was written and how many rows per locale. */
356
+ interface ExportWorkbookResult {
357
+ /** The absolute path the workbook was written to. */
358
+ readonly path: string;
359
+ /** Per-locale row counts, in config order; the same set the workbook carries. */
360
+ readonly locales: readonly {
361
+ readonly locale: string;
362
+ readonly rows: number;
363
+ }[];
364
+ }
365
+ /**
366
+ * Export the strings needing human translation into a styled `.xlsx` workbook. Reuses the same
367
+ * source read, adapter selection, and lock baseline the translate flow uses, runs `diffResources`
368
+ * per target locale to pick the rows (missing and changed by default; add unchanged with
369
+ * `includeUnchanged`), hands the neutral row model to `@verbatra/exchange`'s `buildWorkbook`, and
370
+ * writes the bytes through the {@link SdkFs} seam. No provider is called and no lock-file is written.
371
+ *
372
+ * @param input - The validated config and export options.
373
+ * @param deps - Optional composition seams (registry, file system) for tests.
374
+ * @returns Where the workbook was written and the per-locale row counts.
375
+ * @throws {@link SdkError} `UNKNOWN_FORMAT`, `SOURCE_UNREADABLE`, `SOURCE_INVALID`, `LOCK_FILE_INVALID`
376
+ * with the same meanings as in `translate`.
377
+ */
378
+ declare function exportWorkbook(input: ExportWorkbookInput, deps?: ExportWorkbookDeps): Promise<ExportWorkbookResult>;
379
+
380
+ /** Input for {@link importWorkbook}: the validated config, the workbook path, and run options. */
381
+ interface ImportWorkbookInput {
382
+ /** The validated configuration (typically from {@link loadConfig}). */
383
+ readonly config: VerbatraConfig;
384
+ /** Path to the filled workbook to import. */
385
+ readonly workbook: string;
386
+ /** Directory the file pattern, lock-file, and workbook path resolve against; defaults to cwd. */
387
+ readonly cwd?: string;
388
+ /** When true, validate and report only: write no locale file and update no lock-file. */
389
+ readonly dryRun?: boolean;
390
+ }
391
+ /** Composition seam for {@link importWorkbook}: inject a registry and a file system for tests. */
392
+ interface ImportWorkbookDeps {
393
+ readonly adapterRegistry?: AdapterRegistry;
394
+ readonly fs?: SdkFs;
395
+ }
396
+ /**
397
+ * Import a filled workbook back into the locale files. Reads the untrusted workbook through the
398
+ * SDK's bounded read, parses it with `@verbatra/exchange`'s `readWorkbook` (which bounds and
399
+ * sanitizes it), then for each target-locale data sheet runs the EXISTING core checks (source-drift
400
+ * via `contentHash`, placeholder integrity via `checkPlaceholders`, ICU via the adapter's
401
+ * `validateMessage`), writes the accepted values through the format adapter, and updates the lock
402
+ * through the existing lock logic. Returns a {@link RunSummary} structurally identical to
403
+ * `translate`'s, so the CLI formatter and exit-code rule are shared with no special case.
404
+ *
405
+ * Whole-run failures (unknown format, unreadable/invalid/oversized workbook, corrupt lock) throw a
406
+ * structured {@link SdkError}. A per-sheet failure (a locale not in config, a broken-round-trip key,
407
+ * a write failure) is isolated as that locale's `status: "failed"`, not a throw; per-row rejections
408
+ * are withheld and reported on the locale, exactly as the provider path treats integrity mismatches.
409
+ * `--dry-run` validates and reports without writing any locale or lock file.
410
+ *
411
+ * @param input - The validated config, the workbook path, and run options.
412
+ * @param deps - Optional composition seams (registry, file system) for tests.
413
+ * @returns A {@link RunSummary} with one locale per data sheet, in workbook order.
414
+ * @throws {@link SdkError} `UNKNOWN_FORMAT`, `SOURCE_UNREADABLE`, `SOURCE_INVALID`, `LOCK_FILE_INVALID`.
415
+ */
416
+ declare function importWorkbook(input: ImportWorkbookInput, deps?: ImportWorkbookDeps): Promise<RunSummary>;
417
+
317
418
  /** A minimal source-change event source. Production wraps chokidar; tests inject a stub. */
318
419
  interface Watcher {
319
420
  /** Register a listener invoked once per coalesced source-change event. */
@@ -411,4 +512,4 @@ interface WatchController {
411
512
  */
412
513
  declare function watch(input: WatchInput, deps?: WatchDeps): Promise<WatchController>;
413
514
 
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 };
515
+ export { type CreateProvider, type CreateWatcher, DEFAULT_WORKBOOK_PATH, type ExportWorkbookDeps, type ExportWorkbookInput, type ExportWorkbookResult, type ImportWorkbookDeps, type ImportWorkbookInput, 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, exportWorkbook, importWorkbook, loadConfig, translate, verbatraConfigSchema, watch };