@madewithremy/admin 0.1.14 → 0.1.15

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/prod.js CHANGED
@@ -342,6 +342,62 @@ function statsForMethod(ctx, methodId, params = {}) {
342
342
  );
343
343
  }
344
344
 
345
+ // src/sleep.ts
346
+ function sleep(ms) {
347
+ return new Promise((r) => setTimeout(r, ms));
348
+ }
349
+
350
+ // src/poll.ts
351
+ async function pollUntil(read, isSettled, options) {
352
+ const start2 = Date.now();
353
+ let movedAt = start2;
354
+ let lastKey;
355
+ let transientSince = null;
356
+ while (true) {
357
+ let value;
358
+ try {
359
+ value = await read();
360
+ transientSince = null;
361
+ } catch (err) {
362
+ if (!isTransientError(err)) {
363
+ throw err;
364
+ }
365
+ transientSince ??= Date.now();
366
+ if (Date.now() - transientSince > TRANSIENT_GRACE_MS) {
367
+ throw err;
368
+ }
369
+ options.onProgress?.(
370
+ `retrying after a gateway error\u2026 (${elapsedSeconds(start2)}s)`
371
+ );
372
+ await sleep(options.pollMs);
373
+ continue;
374
+ }
375
+ if (isSettled(value)) {
376
+ return { settled: true, value };
377
+ }
378
+ if (options.progressKey) {
379
+ const key = options.progressKey(value);
380
+ if (key !== lastKey) {
381
+ lastKey = key;
382
+ movedAt = Date.now();
383
+ }
384
+ }
385
+ if (Date.now() - movedAt > options.timeoutMs) {
386
+ return { settled: false, value };
387
+ }
388
+ if (options.describe) {
389
+ options.onProgress?.(options.describe(value, start2));
390
+ }
391
+ await sleep(options.pollMs);
392
+ }
393
+ }
394
+ function elapsedSeconds(start2) {
395
+ return Math.round((Date.now() - start2) / 1e3);
396
+ }
397
+ function timedOut(timeoutMs, still) {
398
+ return `Timed out after ${Math.round(timeoutMs / 1e3)}s ${still}`;
399
+ }
400
+
345
401
  // src/output.ts
346
402
  var PRETTY = process.stdout.isTTY === true;
347
403
  function out(data) {
@@ -352,6 +408,31 @@ function progress(message) {
352
408
  process.stderr.write(`${message}
353
409
  `);
354
410
  }
411
+ function describeEta(eta, now = Date.now()) {
412
+ if (!eta) {
413
+ return null;
414
+ }
415
+ const minutes = Math.max(1, Math.round((Date.parse(eta) - now) / 6e4));
416
+ if (minutes < 90) {
417
+ return `about ${minutes} min left`;
418
+ }
419
+ const hours = Math.round(minutes / 60 * 10) / 10;
420
+ return `about ${hours} h left`;
421
+ }
422
+ function renderProgress(p, start2, extra) {
423
+ const count = p.total !== null ? `${p.done.toLocaleString()}/${p.total.toLocaleString()} ${p.unit}` : p.done > 0 ? `${p.done.toLocaleString()} ${p.unit}` : null;
424
+ const rate = p.ratePerMinute !== null && p.ratePerMinute > 0 ? `${Math.round(p.ratePerMinute).toLocaleString()}/min` : null;
425
+ const parts = [
426
+ `${p.phase}\u2026`,
427
+ count,
428
+ rate,
429
+ describeEta(p.etaAt),
430
+ extra ?? null,
431
+ p.stalled ? "no progress for 30 min" : null
432
+ ].filter((part) => Boolean(part));
433
+ const elapsed = start2 !== void 0 ? ` (${elapsedSeconds(start2)}s)` : "";
434
+ return `${parts.join(" \xB7 ")}${elapsed}`;
435
+ }
355
436
 
356
437
  // src/commands/requests.ts
357
438
  var requestsSpecs = {
@@ -866,11 +947,6 @@ import { execFileSync } from "child_process";
866
947
  // src/config.ts
867
948
  var WORKSPACE_DIR = process.env["WORKSPACE_DIR"] || "/home/vercel-sandbox/workspace";
868
949
 
869
- // src/sleep.ts
870
- function sleep(ms) {
871
- return new Promise((r) => setTimeout(r, ms));
872
- }
873
-
874
950
  // src/ops/releases.ts
875
951
  function list3(ctx, params = {}) {
876
952
  return call(
@@ -2874,50 +2950,6 @@ import path5 from "path";
2874
2950
 
2875
2951
  // src/ops/dataSources.ts
2876
2952
  import { createHash as createHash2 } from "crypto";
2877
-
2878
- // src/poll.ts
2879
- async function pollUntil(read, isSettled, options) {
2880
- const start2 = Date.now();
2881
- let transientSince = null;
2882
- while (true) {
2883
- let value;
2884
- try {
2885
- value = await read();
2886
- transientSince = null;
2887
- } catch (err) {
2888
- if (!isTransientError(err)) {
2889
- throw err;
2890
- }
2891
- transientSince ??= Date.now();
2892
- if (Date.now() - transientSince > TRANSIENT_GRACE_MS) {
2893
- throw err;
2894
- }
2895
- options.onProgress?.(
2896
- `retrying after a gateway error\u2026 (${elapsedSeconds(start2)}s)`
2897
- );
2898
- await sleep(options.pollMs);
2899
- continue;
2900
- }
2901
- if (isSettled(value)) {
2902
- return { settled: true, value };
2903
- }
2904
- if (Date.now() - start2 > options.timeoutMs) {
2905
- return { settled: false, value };
2906
- }
2907
- if (options.describe) {
2908
- options.onProgress?.(options.describe(value, start2));
2909
- }
2910
- await sleep(options.pollMs);
2911
- }
2912
- }
2913
- function elapsedSeconds(start2) {
2914
- return Math.round((Date.now() - start2) / 1e3);
2915
- }
2916
- function timedOut(timeoutMs, still) {
2917
- return `Timed out after ${Math.round(timeoutMs / 1e3)}s ${still}`;
2918
- }
2919
-
2920
- // src/ops/dataSources.ts
2921
2953
  var DEFAULT_WAIT_TIMEOUT_MS = 15 * 60 * 1e3;
2922
2954
  var POLL_MS2 = 3e3;
2923
2955
  function base(appId) {
@@ -2998,6 +3030,7 @@ async function waitForIngest(ctx, params) {
2998
3030
  timeoutMs,
2999
3031
  pollMs: POLL_MS2,
3000
3032
  describe: (tracked2, start2) => `ingesting\u2026 ${tracked2.length - pendingOf(tracked2).length}/${tracked2.length} done (${elapsedSeconds(start2)}s)`,
3033
+ progressKey: (tracked2) => pendingOf(tracked2).length,
3001
3034
  onProgress
3002
3035
  }
3003
3036
  );
@@ -3034,6 +3067,7 @@ async function waitForCandidate(ctx, params) {
3034
3067
  timeoutMs,
3035
3068
  pollMs: POLL_MS2,
3036
3069
  describe: (progress2, start2) => progress2 ? `rebuilding\u2026 ${progress2.done}/${progress2.total} done (${elapsedSeconds(start2)}s)` : `rebuilding\u2026 (${elapsedSeconds(start2)}s)`,
3070
+ progressKey: (progress2) => progress2?.processing ?? null,
3037
3071
  onProgress: params.onProgress
3038
3072
  }
3039
3073
  );
@@ -3186,7 +3220,10 @@ async function waitForMove(ctx, params) {
3186
3220
  {
3187
3221
  timeoutMs,
3188
3222
  pollMs: POLL_MS2,
3189
- describe: (source2, start2) => `moving\u2026 ${source2.migration.copied}/${source2.migration.total} documents (${elapsedSeconds(start2)}s)`,
3223
+ describe: (source2, start2) => renderProgress(source2.migration.progress, start2),
3224
+ // A corpus of any size moves as long as it keeps moving; the timeout
3225
+ // bounds silence.
3226
+ progressKey: (source2) => source2.migration?.copied ?? null,
3190
3227
  onProgress: params.onProgress
3191
3228
  }
3192
3229
  );
@@ -3380,6 +3417,9 @@ async function settle(ctx, params, isSettled) {
3380
3417
  timeoutMs,
3381
3418
  pollMs: POLL_MS3,
3382
3419
  describe: describeProgress,
3420
+ // A load of any length is waited out while it moves; the timeout bounds
3421
+ // silence. Planning moves by objects listed, a run by documents.
3422
+ progressKey: (job2) => `${job2.state}:${job2.progress.done}`,
3383
3423
  onProgress: params.onProgress
3384
3424
  }
3385
3425
  );
@@ -3415,14 +3455,15 @@ async function settle(ctx, params, isSettled) {
3415
3455
  return { status: "done", job };
3416
3456
  }
3417
3457
  function describeProgress(job, start2) {
3418
- const elapsed = `(${elapsedSeconds(start2)}s)`;
3419
- if (job.state === "planning") {
3420
- return job.enumerationDone ? `planning\u2026 reading a sample of ${job.counts.objectsSeen.toLocaleString()} objects ${elapsed}` : `planning\u2026 ${job.counts.objectsSeen.toLocaleString()} objects counted ${elapsed}`;
3458
+ if (job.state === "planning" && job.enumerationDone) {
3459
+ return renderProgress(
3460
+ { ...job.progress, phase: "planning" },
3461
+ start2,
3462
+ `reading a sample of ${job.counts.objectsSeen.toLocaleString()} objects`
3463
+ );
3421
3464
  }
3422
- const total = job.plan?.projected.documents ?? job.counts.objectsSeen;
3423
- const processed = job.counts.documentsDone + job.counts.documentsSkipped + job.counts.documentsFailed;
3424
- const spend = `$${job.estimatedDollars.toFixed(2)}${job.budgetDollars !== null ? ` of $${job.budgetDollars.toFixed(2)}` : ""}`;
3425
- return `${job.state}\u2026 ${processed.toLocaleString()}/${total.toLocaleString()} documents \xB7 ${spend} ${elapsed}`;
3465
+ const spend = job.state === "planning" ? null : `$${job.estimatedDollars.toFixed(2)}${job.budgetDollars !== null ? ` of $${job.budgetDollars.toFixed(2)}` : ""}`;
3466
+ return renderProgress(job.progress, start2, spend);
3426
3467
  }
3427
3468
 
3428
3469
  // src/commands/_shared/dataSources.ts
@@ -3690,7 +3731,7 @@ function hydrate(ctx, params) {
3690
3731
  ctx,
3691
3732
  "POST",
3692
3733
  `${base4(ctx.appId)}/hydrate`,
3693
- { slug: params.slug, ...params.since ? { since: params.since } : {} }
3734
+ { slug: params.slug }
3694
3735
  );
3695
3736
  }
3696
3737
  function reindex(ctx, params) {
@@ -3716,7 +3757,10 @@ async function waitForHydration(ctx, params) {
3716
3757
  {
3717
3758
  timeoutMs,
3718
3759
  pollMs: POLL_MS4,
3719
- describe: (source2, start2) => `loading\u2026 ${source2.hydration.copied}/${source2.hydration.total} documents (${elapsedSeconds(start2)}s)`,
3760
+ describe: (source2, start2) => renderProgress(source2.hydration.progress, start2),
3761
+ // A reload of any size is waited out while it moves; the timeout
3762
+ // bounds silence.
3763
+ progressKey: (source2) => source2.hydration?.copied ?? null,
3720
3764
  onProgress: params.onProgress
3721
3765
  }
3722
3766
  );
@@ -3904,10 +3948,9 @@ async function waitForSet(ctx, params) {
3904
3948
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3905
3949
  var dataSourceEvalsSpecs = {
3906
3950
  "datasources hydrate": {
3907
- usage: "Usage: remy-admin datasources hydrate [--source <slug>] [--since <iso>] [--wait] [--timeout <sec>]",
3951
+ usage: "Usage: remy-admin datasources hydrate [--source <slug>] [--wait] [--timeout <sec>]",
3908
3952
  flags: {
3909
3953
  source: { type: "string" },
3910
- since: { type: "string" },
3911
3954
  wait: { type: "boolean" },
3912
3955
  timeout: { type: "string" }
3913
3956
  }
@@ -4078,14 +4121,7 @@ function selectorFromFlag(raw) {
4078
4121
  }
4079
4122
  async function hydrate2(ctx, a) {
4080
4123
  const slug = a.str("source") || DEFAULT_SOURCE;
4081
- const since = a.str("since");
4082
- if (since && Number.isNaN(Date.parse(since))) {
4083
- fatal("--since must be an ISO 8601 timestamp, e.g. 2026-09-08T15:17:00Z.");
4084
- }
4085
- const started = await hydrate(ctx, {
4086
- slug,
4087
- ...since ? { since: new Date(since).toISOString() } : {}
4088
- });
4124
+ const started = await hydrate(ctx, { slug });
4089
4125
  if (started.resident || !a.bool("wait")) {
4090
4126
  out({
4091
4127
  dataSource: slug,
@@ -4380,7 +4416,7 @@ var dataSourceEvalsHandlers = {
4380
4416
  };
4381
4417
  var dataSourceEvalsHelp = `
4382
4418
  Warming and sampling:
4383
- remy-admin datasources hydrate [--source <slug>] [--since <iso>] [--wait]
4419
+ remy-admin datasources hydrate [--source <slug>] [--wait]
4384
4420
  remy-admin datasources sample --source <slug> --size <n> [--as <slug>] [--filter <k=v,...|json>] [--stratify <key>] [--placement <id|shared>] [--wait]
4385
4421
 
4386
4422
  A large source whose index was evicted (the shared pool holds a working set,
@@ -4388,15 +4424,9 @@ Warming and sampling:
4388
4424
  background; small sources reload inside the search and never show it.
4389
4425
  \`hydrate\` starts that reload ahead of time \u2014 before a demo, say \u2014 and
4390
4426
  \`--wait\` blocks until the index is back. \`datasources list\` shows the
4391
- reload's progress under \`hydration\`.
4392
-
4393
- \`hydrate --since <iso>\` is a refill rather than a warm-up: it copies every
4394
- document built at or after that time into the index whether or not the index
4395
- is resident. Dedicated capacity that restored from a snapshot is missing
4396
- everything written after the snapshot was taken; the platform starts this
4397
- refill itself when it sees the restore, and \`infra logs\` shows it. Use the
4398
- flag for a restore that predates that, with the snapshot's time from the
4399
- resource log.
4427
+ reload's progress under \`hydration\`. The same reload is what dedicated
4428
+ capacity runs on its own after a lost node or a resume; \`infra logs\` shows
4429
+ it start, and the source's \`hydration\` shows it land.
4400
4430
 
4401
4431
  \`sample\` draws documents from a source into a new one that shares its
4402
4432
  exact pinned config, so what you measure on the sample says something about
@@ -7643,7 +7673,14 @@ async function waitForPhase(ctx, params) {
7643
7673
  {
7644
7674
  timeoutMs,
7645
7675
  pollMs: POLL_MS5,
7646
- describe: (resource2, start2) => `${resource2.phase}${resource2.detail ? ` \xB7 ${resource2.detail}` : ""}${resource2.resizingTo ? ` \xB7 resizing to ${resource2.resizingTo.label}` : ""}\u2026 (${elapsedSeconds(start2)}s)`,
7676
+ describe: (resource2, start2) => resource2.progress ? renderProgress(
7677
+ resource2.progress,
7678
+ start2,
7679
+ resource2.resizingTo ? `resizing to ${resource2.resizingTo.label}` : null
7680
+ ) : `${resource2.phase}${resource2.detail ? ` \xB7 ${resource2.detail}` : ""}${resource2.resizingTo ? ` \xB7 resizing to ${resource2.resizingTo.label}` : ""}\u2026 (${elapsedSeconds(start2)}s)`,
7681
+ // A rebuild after a resume runs as long as it must; the timeout bounds
7682
+ // silence, and a phase or step change counts as movement.
7683
+ progressKey: (resource2) => `${resource2.phase}:${resource2.progress?.done ?? ""}:${resource2.detail ?? ""}`,
7647
7684
  onProgress: params.onProgress
7648
7685
  }
7649
7686
  );
@@ -7929,26 +7966,29 @@ Placing a data source on a resource:
7929
7966
 
7930
7967
  Searches on a source whose resource is not active fail with
7931
7968
  capacity_<phase>: capacity_hibernated once parked, capacity_hibernating /
7932
- capacity_resuming while it moves. \`active\` means searchable: a resumed
7933
- resource reports active only once its data is restored. capacity_restoring
7934
- is the rare case of an active instance that was replaced (node loss) and is
7935
- refilling from its snapshot. The messages are written for the app's end
7936
- user; the stable code is what to act on.
7969
+ capacity_resuming while it moves. Once active, a source whose index is being
7970
+ rebuilt from durable storage (after a resume, a resize or a lost node)
7971
+ answers index_warming with the rebuild's progress and ETA until it lands;
7972
+ \`datasources list\` shows it under hydration and the resource under
7973
+ progress. The messages are written for the app's end user; the stable code
7974
+ is what to act on.
7937
7975
 
7938
7976
  Resizing:
7939
- A resize keeps the data. The resource is parked (a fresh snapshot), its spec
7940
- is swapped, and it comes back up restored from that snapshot, so searches on
7941
- its sources pause for the few minutes it takes. A hibernated resource swaps
7942
- in place and stays parked. Growing needs a month's credits at the new rate;
7943
- shrinking is refused below what the resource holds (resize_too_small).
7944
- \`infra get\` shows resizingTo until the swap has happened.
7977
+ A resize keeps the documents: the resource is parked, its spec is swapped,
7978
+ and it comes back up with its sources' indexes rebuilt from durable storage
7979
+ in the background (nothing is re-embedded). Searches on those sources answer
7980
+ index_warming until the rebuild lands. A hibernated resource swaps in place
7981
+ and stays parked. Growing needs a month's credits at the new rate; shrinking
7982
+ is refused below what the resource holds (resize_too_small). \`infra get\`
7983
+ shows resizingTo until the swap has happened.
7945
7984
 
7946
7985
  Phases and timing:
7947
7986
  requested \u2192 provisioning \u2192 active \u21C4 hibernated \u2192 destroyed, with hibernating /
7948
7987
  resuming / decommissioning in between and failed when a change cannot complete
7949
- (retried automatically; resume or hibernate to retry by hand). Each transition
7950
- takes about two minutes, more with more data: snapshots and restores scale
7951
- with the collection. --wait defaults to 600s; pass --timeout for a large one.
7988
+ (retried automatically; resume or hibernate to retry by hand). A transition
7989
+ takes a minute or two; the rebuild that follows a resume runs under its own
7990
+ progress with a measured ETA. --wait gives up only after --timeout (default
7991
+ 600s) WITHOUT movement, so a long rebuild is waited out while it moves.
7952
7992
 
7953
7993
  Billing and the log:
7954
7994
  \`infra get\` reports resource.billing: what the resource has actually been
@@ -7957,7 +7997,8 @@ Billing and the log:
7957
7997
 
7958
7998
  The log is the platform's narration of the resource, not the instance's
7959
7999
  stdout: each step a transition went through ("Waiting for capacity",
7960
- "Restoring collections 2/3"), each failed attempt, and each failure with the
8000
+ "Rebuilding archive v1 from its artifacts: 14,333,673 document(s)"), each
8001
+ failed attempt, and each failure with the
7961
8002
  pod's recent Kubernetes events and the warnings-and-errors tail of the
7962
8003
  instance log at that moment (line.data). \`infra get\` includes the newest
7963
8004
  200 lines; \`infra logs\` pages the rest and, with --follow, prints new lines
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@madewithremy/admin",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "Admin SDK + CLI for managing production Remy apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",