@zero-transfer/mft 0.4.0 → 0.4.2

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.ts CHANGED
@@ -833,7 +833,41 @@ interface TransferEngineOptions {
833
833
  /** Clock used for receipts and progress events. Defaults to `new Date()`. */
834
834
  now?: () => Date;
835
835
  }
836
- /** Executes transfer jobs and produces audit-friendly receipts. */
836
+ /**
837
+ * Executes transfer jobs and produces audit-friendly receipts.
838
+ *
839
+ * The engine is the lowest-level entry point in the transfer stack: it owns
840
+ * retry policy, attempt history, abort propagation, progress event
841
+ * normalization, and receipt construction. Most callers reach the engine
842
+ * indirectly through {@link runRoute}, {@link uploadFile}, {@link downloadFile},
843
+ * {@link copyBetween}, or {@link TransferQueue}; instantiate it directly when
844
+ * you need full control over execution semantics.
845
+ *
846
+ * @example Execute a single job with a custom executor
847
+ * ```ts
848
+ * import { TransferEngine, type TransferExecutor, type TransferJob } from "@zero-transfer/sdk";
849
+ *
850
+ * const engine = new TransferEngine();
851
+ *
852
+ * const executor: TransferExecutor = async ({ job, signal, onProgress }) => {
853
+ * onProgress?.({ jobId: job.id, bytesTransferred: 0 });
854
+ * // … perform the bytes here, honoring `signal` …
855
+ * return { jobId: job.id, bytesTransferred: 1234, completedAt: new Date() };
856
+ * };
857
+ *
858
+ * const job: TransferJob = {
859
+ * id: "manual-1",
860
+ * operation: "upload",
861
+ * source: { profile: localProfile, path: "./data.bin" },
862
+ * destination: { profile: s3Profile, path: "/data/data.bin" },
863
+ * };
864
+ *
865
+ * const receipt = await engine.execute(job, executor, {
866
+ * retry: { maxAttempts: 3, baseDelayMs: 250 },
867
+ * });
868
+ * console.log(receipt.attempts.length); // 1 on success
869
+ * ```
870
+ */
837
871
  declare class TransferEngine {
838
872
  private readonly now;
839
873
  /**
@@ -1416,9 +1450,41 @@ interface RunRouteOptions {
1416
1450
  /**
1417
1451
  * Executes an MFT route as a single transfer through the supplied client.
1418
1452
  *
1453
+ * Connects the source and destination profiles, runs the route's transfer
1454
+ * through the engine, and returns the resulting receipt. The friendly helpers
1455
+ * {@link uploadFile}, {@link downloadFile}, and {@link copyBetween} synthesize
1456
+ * routes and delegate to this function, so behaviour around retry, abort,
1457
+ * progress, timeout, and bandwidth limits is identical.
1458
+ *
1419
1459
  * @param options - Client, route, and optional engine/abort/retry hooks.
1420
1460
  * @returns Receipt produced by the underlying transfer engine.
1421
1461
  * @throws {@link ConfigurationError} When the route is disabled.
1462
+ *
1463
+ * @example Run a pre-built route with progress + retry
1464
+ * ```ts
1465
+ * import { createTransferClient, runRoute, type MftRoute } from "@zero-transfer/sdk";
1466
+ *
1467
+ * const route: MftRoute = {
1468
+ * id: "nightly-export",
1469
+ * operation: "copy",
1470
+ * source: {
1471
+ * path: "/exports/daily.csv",
1472
+ * profile: { host: "sftp.example.com", provider: "sftp", username: "etl" },
1473
+ * },
1474
+ * destination: {
1475
+ * path: "warehouse/daily.csv",
1476
+ * profile: { host: "warehouse", provider: "s3", s3: { region: "us-east-1" } },
1477
+ * },
1478
+ * };
1479
+ *
1480
+ * const receipt = await runRoute({
1481
+ * client,
1482
+ * route,
1483
+ * onProgress: (e) => console.log(`${e.bytesTransferred}/${e.totalBytes ?? "?"}`),
1484
+ * retry: { maxAttempts: 3, baseDelayMs: 500 },
1485
+ * });
1486
+ * console.log(`Job ${receipt.jobId} moved ${receipt.bytesTransferred} bytes…`);
1487
+ * ```
1422
1488
  */
1423
1489
  declare function runRoute(options: RunRouteOptions): Promise<TransferReceipt>;
1424
1490
 
@@ -1592,8 +1658,20 @@ interface ClientDiagnostics {
1592
1658
  /**
1593
1659
  * Returns a redaction-safe snapshot of the providers registered with a client.
1594
1660
  *
1661
+ * Use this when rendering a setup screen, generating a support bundle, or
1662
+ * asserting in tests that the expected provider factories were registered.
1663
+ *
1595
1664
  * @param client - Transfer client to inspect.
1596
1665
  * @returns Provider id and capability snapshot tuples.
1666
+ *
1667
+ * @example List registered providers
1668
+ * ```ts
1669
+ * import { summarizeClientDiagnostics } from "@zero-transfer/sdk";
1670
+ *
1671
+ * for (const { id, capabilities } of summarizeClientDiagnostics(client).providers) {
1672
+ * console.log(`${id}: streaming=${capabilities.readStream} resume=${capabilities.resumeDownload}`);
1673
+ * }
1674
+ * ```
1597
1675
  */
1598
1676
  declare function summarizeClientDiagnostics(client: TransferClient): ClientDiagnostics;
1599
1677
  /** Per-step duration measurements collected by {@link runConnectionDiagnostics}. */
@@ -1646,8 +1724,36 @@ interface RunConnectionDiagnosticsOptions {
1646
1724
  /**
1647
1725
  * Connects to a profile, captures capability and listing samples, and returns a redaction-safe report.
1648
1726
  *
1727
+ * Useful for connectivity "ping" pages, smoke tests, and bug reports. Secrets
1728
+ * in the profile are redacted via {@link redactConnectionProfile} before being
1729
+ * returned. The session is always disconnected before the function returns,
1730
+ * including when probes throw.
1731
+ *
1649
1732
  * @param options - Diagnostic probe options.
1650
1733
  * @returns Diagnostic report including timings and any captured error.
1734
+ *
1735
+ * @example Probe an SFTP connection
1736
+ * ```ts
1737
+ * import { runConnectionDiagnostics } from "@zero-transfer/sdk";
1738
+ *
1739
+ * const report = await runConnectionDiagnostics({
1740
+ * client,
1741
+ * profile: {
1742
+ * host: "sftp.example.com",
1743
+ * provider: "sftp",
1744
+ * username: "deploy",
1745
+ * ssh: { privateKey: { path: "./keys/id_ed25519" } },
1746
+ * },
1747
+ * listPath: "/uploads",
1748
+ * });
1749
+ *
1750
+ * if (!report.ok) {
1751
+ * console.error("connection failed:", report.error);
1752
+ * } else {
1753
+ * console.log(`connect=${report.timings.connectMs}ms list=${report.timings.listMs}ms`);
1754
+ * console.log(report.sample); // up to 5 entries from /uploads
1755
+ * }
1756
+ * ```
1651
1757
  */
1652
1758
  declare function runConnectionDiagnostics(options: RunConnectionDiagnosticsOptions): Promise<ConnectionDiagnosticsResult>;
1653
1759
 
@@ -1754,8 +1860,29 @@ interface MemoryProviderOptions {
1754
1860
  /**
1755
1861
  * Creates a provider factory backed by deterministic in-memory fixture entries.
1756
1862
  *
1863
+ * Useful for tests and examples where you want a real `TransferSession` without
1864
+ * touching disk or the network. Entries are pre-seeded; mutations made through
1865
+ * the session are visible to subsequent operations on the same provider.
1866
+ *
1757
1867
  * @param options - Optional fixture entries to expose through the memory provider.
1758
1868
  * @returns Provider factory suitable for `createTransferClient({ providers: [...] })`.
1869
+ *
1870
+ * @example Seed entries and read them back
1871
+ * ```ts
1872
+ * import { createMemoryProviderFactory, createTransferClient } from "@zero-transfer/sdk";
1873
+ *
1874
+ * const client = createTransferClient({
1875
+ * providers: [createMemoryProviderFactory({
1876
+ * entries: [
1877
+ * { path: "/fixtures/hello.txt", content: "hello world" },
1878
+ * { path: "/fixtures/data.bin", content: new Uint8Array([1, 2, 3]) },
1879
+ * ],
1880
+ * })],
1881
+ * });
1882
+ *
1883
+ * const session = await client.connect({ host: "fixtures", provider: "memory" });
1884
+ * console.log(await session.fs.list("/fixtures"));
1885
+ * ```
1759
1886
  */
1760
1887
  declare function createMemoryProviderFactory(options?: MemoryProviderOptions): ProviderFactory;
1761
1888
 
@@ -2493,9 +2620,47 @@ interface TransferPlanSummary {
2493
2620
  /** Counts grouped by action. */
2494
2621
  actions: Record<string, number>;
2495
2622
  }
2496
- /** Creates a transfer plan from dry-run planning input. */
2623
+ /**
2624
+ * Creates a transfer plan from dry-run planning input.
2625
+ *
2626
+ * Plans are immutable, structured descriptions of intended work. Pair with
2627
+ * {@link createSyncPlan} or {@link createAtomicDeployPlan} for end-to-end
2628
+ * planning, or build steps by hand when you need full control. Pass the plan
2629
+ * to {@link createTransferJobsFromPlan} to materialize executable jobs.
2630
+ *
2631
+ * @example Build a plan with two upload steps and inspect it
2632
+ * ```ts
2633
+ * import { createTransferPlan, summarizeTransferPlan } from "@zero-transfer/sdk";
2634
+ *
2635
+ * const plan = createTransferPlan({
2636
+ * id: "manual-batch",
2637
+ * steps: [
2638
+ * { action: "upload", source: "./a.bin", destination: "/lake/a.bin", expectedBytes: 1024 },
2639
+ * { action: "upload", source: "./b.bin", destination: "/lake/b.bin", expectedBytes: 2048 },
2640
+ * ],
2641
+ * });
2642
+ *
2643
+ * console.table(summarizeTransferPlan(plan));
2644
+ * ```
2645
+ */
2497
2646
  declare function createTransferPlan(input: TransferPlanInput): TransferPlan;
2498
- /** Summarizes a transfer plan for diagnostics, previews, and tests. */
2647
+ /**
2648
+ * Summarizes a transfer plan for diagnostics, previews, and tests.
2649
+ *
2650
+ * Returns aggregate counts (total / executable / skipped / destructive),
2651
+ * total expected bytes, and a per-action histogram. Useful for printing a
2652
+ * one-line plan summary before executing or for asserting plan shape in
2653
+ * tests.
2654
+ *
2655
+ * @example Print a plan preview
2656
+ * ```ts
2657
+ * import { summarizeTransferPlan } from "@zero-transfer/sdk";
2658
+ *
2659
+ * const summary = summarizeTransferPlan(plan);
2660
+ * console.log(`${summary.executableSteps} steps, ${summary.totalExpectedBytes} bytes total`);
2661
+ * console.log("Actions:", summary.actions);
2662
+ * ```
2663
+ */
2499
2664
  declare function summarizeTransferPlan(plan: TransferPlan): TransferPlanSummary;
2500
2665
  /** Converts executable plan steps into transfer jobs while preserving order. */
2501
2666
  declare function createTransferJobsFromPlan(plan: TransferPlan): TransferJob[];
@@ -2572,7 +2737,41 @@ interface TransferQueueSummary {
2572
2737
  /** Failed queue items in queue order. */
2573
2738
  failures: TransferQueueItem[];
2574
2739
  }
2575
- /** Minimal transfer queue with concurrency, pause/resume, cancellation, and drain summaries. */
2740
+ /**
2741
+ * Minimal transfer queue with concurrency, pause/resume, cancellation, and drain summaries.
2742
+ *
2743
+ * Wrap a {@link TransferEngine} with a queue when you need to run many transfers
2744
+ * concurrently with bounded parallelism, observe per-job progress, or drive
2745
+ * a UI from a single source of truth. Items are FIFO; failures and successes
2746
+ * are surfaced via observers and in the final {@link TransferQueueSummary}.
2747
+ *
2748
+ * @example Run a batch of uploads with concurrency=4
2749
+ * ```ts
2750
+ * import {
2751
+ * TransferQueue,
2752
+ * createProviderTransferExecutor,
2753
+ * } from "@zero-transfer/sdk";
2754
+ *
2755
+ * const queue = new TransferQueue({
2756
+ * concurrency: 4,
2757
+ * executor: createProviderTransferExecutor({ client }),
2758
+ * onProgress: (e) => console.log(`${e.jobId}: ${e.bytesTransferred}`),
2759
+ * onError: (item, err) => console.error(`${item.job.id} failed`, err),
2760
+ * });
2761
+ *
2762
+ * for (const file of files) {
2763
+ * queue.enqueue({
2764
+ * id: file.name,
2765
+ * operation: "upload",
2766
+ * source: { profile: localProfile, path: file.path },
2767
+ * destination: { profile: s3Profile, path: `/lake/${file.name}` },
2768
+ * });
2769
+ * }
2770
+ *
2771
+ * const summary = await queue.drain();
2772
+ * console.log(`Completed ${summary.completed} / ${summary.total}`);
2773
+ * ```
2774
+ */
2576
2775
  declare class TransferQueue {
2577
2776
  private readonly engine;
2578
2777
  private readonly items;
@@ -2852,6 +3051,26 @@ interface DiffRemoteTreesOptions {
2852
3051
  * @param destinationPath - Destination-side root path being compared.
2853
3052
  * @param options - Optional comparison controls.
2854
3053
  * @returns Diff result containing entries and a summary.
3054
+ *
3055
+ * @example Diff two SFTP subtrees and feed the result into createSyncPlan
3056
+ * ```ts
3057
+ * import { createSyncPlan, diffRemoteTrees } from "@zero-transfer/sdk";
3058
+ *
3059
+ * const diff = await diffRemoteTrees(
3060
+ * srcSession.fs, "/exports",
3061
+ * dstSession.fs, "/exports",
3062
+ * { compareUniqueId: true },
3063
+ * );
3064
+ *
3065
+ * console.log(diff.summary); // { added, removed, changed, unchanged }
3066
+ *
3067
+ * const plan = createSyncPlan({
3068
+ * id: "exports-sync",
3069
+ * diff,
3070
+ * source: { provider: "sftp", rootPath: "/exports" },
3071
+ * destination: { provider: "sftp", rootPath: "/exports" },
3072
+ * });
3073
+ * ```
2855
3074
  */
2856
3075
  declare function diffRemoteTrees(source: RemoteFileSystem, sourcePath: string, destination: RemoteFileSystem, destinationPath: string, options?: DiffRemoteTreesOptions): Promise<RemoteTreeDiff>;
2857
3076
 
@@ -2923,6 +3142,31 @@ interface CreateSyncPlanOptions {
2923
3142
  * @param options - Inputs and policies that shape the plan.
2924
3143
  * @returns Transfer plan ready for `createTransferJobsFromPlan` or queue execution.
2925
3144
  * @throws {@link ConfigurationError} When `conflictPolicy: "error"` encounters a conflict.
3145
+ *
3146
+ * @example Mirror SFTP → S3 with deletes
3147
+ * ```ts
3148
+ * import {
3149
+ * createSyncPlan,
3150
+ * diffRemoteTrees,
3151
+ * summarizeTransferPlan,
3152
+ * } from "@zero-transfer/sdk";
3153
+ *
3154
+ * const diff = await diffRemoteTrees(
3155
+ * srcSession.fs, "/dist",
3156
+ * dstSession.fs, "/releases/current",
3157
+ * );
3158
+ *
3159
+ * const plan = createSyncPlan({
3160
+ * id: "release-mirror",
3161
+ * diff,
3162
+ * source: { provider: "sftp", rootPath: "/dist" },
3163
+ * destination: { provider: "s3", rootPath: "/releases/current" },
3164
+ * deletePolicy: "mirror",
3165
+ * conflictPolicy: "overwrite",
3166
+ * });
3167
+ *
3168
+ * console.table(summarizeTransferPlan(plan));
3169
+ * ```
2926
3170
  */
2927
3171
  declare function createSyncPlan(options: CreateSyncPlanOptions): TransferPlan;
2928
3172
 
@@ -3038,9 +3282,39 @@ interface CreateAtomicDeployPlanOptions {
3038
3282
  /**
3039
3283
  * Builds an {@link AtomicDeployPlan} that stages a release, swaps it live, and prunes old releases.
3040
3284
  *
3285
+ * The plan describes a blue/green-style deploy:
3286
+ * 1. Upload to a timestamped staging directory under `<destination>/.releases/`.
3287
+ * 2. Atomically swap the `current` symlink/rename to point at the new release.
3288
+ * 3. Optionally prune old releases beyond `retain`.
3289
+ *
3290
+ * No I/O is performed — the host executes the plan steps. Pair with
3291
+ * {@link createTransferPlan} or {@link createTransferJobsFromPlan} to execute.
3292
+ *
3041
3293
  * @param options - Inputs and policies that shape the deploy.
3042
3294
  * @returns Structured deploy plan ready for execution by the calling host.
3043
3295
  * @throws {@link ConfigurationError} When `retain` is less than `1` or the destination root is empty.
3296
+ *
3297
+ * @example Plan a release with rollback path
3298
+ * ```ts
3299
+ * import { createAtomicDeployPlan } from "@zero-transfer/sdk";
3300
+ *
3301
+ * const plan = createAtomicDeployPlan({
3302
+ * id: "web-2026-04-28",
3303
+ * source: { rootPath: "./dist" },
3304
+ * destination: {
3305
+ * profile: { host: "web1.example.com", provider: "sftp", username: "deploy" },
3306
+ * rootPath: "/srv/www",
3307
+ * },
3308
+ * retain: 5,
3309
+ * existingReleases: [
3310
+ * "/srv/www/.releases/2026-04-21T00-00-00Z",
3311
+ * "/srv/www/.releases/2026-04-14T00-00-00Z",
3312
+ * ],
3313
+ * });
3314
+ *
3315
+ * console.log(plan.swap); // staging → current rename
3316
+ * console.log(plan.prune); // releases scheduled for removal
3317
+ * ```
3044
3318
  */
3045
3319
  declare function createAtomicDeployPlan(options: CreateAtomicDeployPlanOptions): AtomicDeployPlan;
3046
3320
 
@@ -3634,9 +3908,33 @@ interface CreateWebhookAuditLogOptions {
3634
3908
  *
3635
3909
  * Entries whose `type` is not in `target.types` are silently dropped. `list()`
3636
3910
  * always returns an empty array because webhook deliveries are not buffered.
3911
+ * Payloads are HMAC-signed with `target.secret` (when provided) so receivers
3912
+ * can verify authenticity before acting on them.
3637
3913
  *
3638
3914
  * @param options - Webhook target plus optional retry/observer hooks.
3639
3915
  * @returns An audit log that delivers each `record` call to the webhook.
3916
+ *
3917
+ * @example Compose a webhook log with an in-memory log for local replay
3918
+ * ```ts
3919
+ * import {
3920
+ * InMemoryAuditLog,
3921
+ * composeAuditLogs,
3922
+ * createWebhookAuditLog,
3923
+ * } from "@zero-transfer/sdk";
3924
+ *
3925
+ * const memory = new InMemoryAuditLog();
3926
+ * const webhook = createWebhookAuditLog({
3927
+ * target: {
3928
+ * url: "https://hooks.example.com/zt",
3929
+ * secret: { env: "ZT_WEBHOOK_SECRET" },
3930
+ * types: ["transfer.success", "transfer.failure"],
3931
+ * },
3932
+ * onDelivery: ({ result }) => console.log("delivered", result.statusCode),
3933
+ * });
3934
+ *
3935
+ * const audit = composeAuditLogs(memory, webhook);
3936
+ * await audit.record({ type: "transfer.success", receipt });
3937
+ * ```
3640
3938
  */
3641
3939
  declare function createWebhookAuditLog(options: CreateWebhookAuditLogOptions): MftAuditLog;
3642
3940
 
@@ -3791,7 +4089,48 @@ interface MftSchedulerOptions {
3791
4089
  /** Timer/clock injection used by tests. */
3792
4090
  timer?: ScheduleTimerHooks;
3793
4091
  }
3794
- /** Runs routes on configured schedules. */
4092
+ /**
4093
+ * Runs routes on configured schedules.
4094
+ *
4095
+ * Subscribes to a {@link ScheduleRegistry}, computes the next fire time for
4096
+ * each schedule (cron or interval), and dispatches the matching route through
4097
+ * a runner of your choice (`runRoute` by default, or a wrapped runner for
4098
+ * approvals / rate limiting / circuit breaking). Observers fire on each cycle
4099
+ * for telemetry. Tests can inject a deterministic timer via `timer`.
4100
+ *
4101
+ * @example Wire a cron schedule with audit + approval
4102
+ * ```ts
4103
+ * import {
4104
+ * ApprovalRegistry,
4105
+ * InMemoryAuditLog,
4106
+ * MftScheduler,
4107
+ * RouteRegistry,
4108
+ * ScheduleRegistry,
4109
+ * createApprovalGate,
4110
+ * runRoute,
4111
+ * } from "@zero-transfer/sdk";
4112
+ *
4113
+ * const audit = new InMemoryAuditLog();
4114
+ * const approvals = new ApprovalRegistry();
4115
+ *
4116
+ * const scheduler = new MftScheduler({
4117
+ * client,
4118
+ * routes: new RouteRegistry([route]),
4119
+ * schedules: new ScheduleRegistry([
4120
+ * { id: "nightly", routeId: route.id, cron: "0 2 * * *" },
4121
+ * ]),
4122
+ * runner: createApprovalGate({
4123
+ * registry: approvals,
4124
+ * approvalId: ({ route }) => `release:${route.id}`,
4125
+ * runner: ({ client: c, route: r, signal }) => runRoute({ client: c, route: r, signal }),
4126
+ * }),
4127
+ * onResult: ({ receipt }) => audit.record({ type: "transfer.success", receipt }),
4128
+ * onError: ({ error }) => audit.record({ type: "transfer.failure", error }),
4129
+ * });
4130
+ *
4131
+ * scheduler.start();
4132
+ * ```
4133
+ */
3795
4134
  declare class MftScheduler {
3796
4135
  private readonly options;
3797
4136
  private readonly now;
@@ -3935,10 +4274,33 @@ interface CreateApprovalGateOptions {
3935
4274
  *
3936
4275
  * The returned runner creates an approval request, waits for resolution, and
3937
4276
  * dispatches the underlying runner only when the request is approved. Rejection
3938
- * surfaces an {@link ApprovalRejectedError}.
4277
+ * surfaces an {@link ApprovalRejectedError}. Pair with {@link MftScheduler} to
4278
+ * implement two-person rules and human-in-the-loop release flows.
3939
4279
  *
3940
4280
  * @param options - Registry, downstream runner, approval-id derivation, hooks.
3941
4281
  * @returns A {@link ScheduleRouteRunner} that gates execution behind approval.
4282
+ *
4283
+ * @example Two-person rule on a release route
4284
+ * ```ts
4285
+ * import {
4286
+ * ApprovalRegistry,
4287
+ * createApprovalGate,
4288
+ * runRoute,
4289
+ * } from "@zero-transfer/sdk";
4290
+ *
4291
+ * const approvals = new ApprovalRegistry();
4292
+ *
4293
+ * const gatedRunner = createApprovalGate({
4294
+ * registry: approvals,
4295
+ * approvalId: ({ route }) => `release:${route.id}:${Date.now()}`,
4296
+ * onRequested: (req) => notifyOnCallChannel(req),
4297
+ * runner: ({ client, route, signal }) => runRoute({ client, route, signal }),
4298
+ * });
4299
+ *
4300
+ * // Elsewhere, an authorized operator approves or rejects:
4301
+ * approvals.approve(approvalId, { actor: "alice@example.com" });
4302
+ * // approvals.reject(approvalId, { actor: "bob@example.com", reason: "hold release" });
4303
+ * ```
3942
4304
  */
3943
4305
  declare function createApprovalGate(options: CreateApprovalGateOptions): ScheduleRouteRunner;
3944
4306