@madewithremy/admin 0.1.6 → 0.1.7

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
@@ -3079,6 +3079,61 @@ function create2(ctx, params) {
3079
3079
  ...placement !== void 0 ? { placement } : {}
3080
3080
  });
3081
3081
  }
3082
+ function move(ctx, params) {
3083
+ return call(ctx, "POST", `${base(ctx.appId)}/move`, {
3084
+ slug: params.slug,
3085
+ placement: params.placement
3086
+ });
3087
+ }
3088
+ async function waitForMove(ctx, params) {
3089
+ const timeoutMs = params.timeoutMs ?? DEFAULT_WAIT_TIMEOUT_MS;
3090
+ const start = Date.now();
3091
+ let transientSince = null;
3092
+ for (; ; ) {
3093
+ let source;
3094
+ try {
3095
+ const { dataSources } = await list8(ctx);
3096
+ source = dataSources.find((s) => s.slug === params.slug);
3097
+ transientSince = null;
3098
+ } catch (err) {
3099
+ if (!isTransientError(err)) {
3100
+ throw err;
3101
+ }
3102
+ transientSince ??= Date.now();
3103
+ if (Date.now() - transientSince > TRANSIENT_GRACE_MS) {
3104
+ throw err;
3105
+ }
3106
+ params.onProgress?.(
3107
+ `retrying after a gateway error\u2026 (${Math.round((Date.now() - start) / 1e3)}s)`
3108
+ );
3109
+ await sleep(POLL_MS2);
3110
+ continue;
3111
+ }
3112
+ if (!source) {
3113
+ throw new Error(`Data source "${params.slug}" not found.`);
3114
+ }
3115
+ const migration = source.migration;
3116
+ if (!migration) {
3117
+ return { status: "done", source };
3118
+ }
3119
+ if (migration.error) {
3120
+ return { status: "failed", source, error: migration.error };
3121
+ }
3122
+ if (Date.now() - start > timeoutMs) {
3123
+ return {
3124
+ status: "timeout",
3125
+ source,
3126
+ error: `Timed out after ${Math.round(timeoutMs / 1e3)}s with ${migration.copied}/${migration.total} documents copied`
3127
+ };
3128
+ }
3129
+ params.onProgress?.(
3130
+ `moving\u2026 ${migration.copied}/${migration.total} documents (${Math.round(
3131
+ (Date.now() - start) / 1e3
3132
+ )}s)`
3133
+ );
3134
+ await sleep(POLL_MS2);
3135
+ }
3136
+ }
3082
3137
  function revectorize(ctx, params) {
3083
3138
  const { slug, ingest } = params;
3084
3139
  return call(
@@ -3216,11 +3271,24 @@ var dataSourcesSpecs = {
3216
3271
  flags: {
3217
3272
  source: { type: "string" },
3218
3273
  // Where the corpus lives: a dedicated resource id from `infra list`, or
3219
- // `shared`. Only changeable while the source has nothing built.
3274
+ // `shared`. A populated source starts a background move (see `move`).
3220
3275
  placement: { type: "string" },
3221
3276
  ...CONFIG_FLAGS
3222
3277
  }
3223
3278
  },
3279
+ "datasources move": {
3280
+ usage: "Usage: remy-admin datasources move [--source <slug>] --to <resource-id|shared> [--wait] [--timeout <sec>]",
3281
+ flags: {
3282
+ source: { type: "string" },
3283
+ to: { type: "string" },
3284
+ wait: { type: "boolean" },
3285
+ timeout: { type: "string" }
3286
+ },
3287
+ requireAnyOf: {
3288
+ flags: ["to"],
3289
+ message: "--to is required: a resource id from `infra list`, or `shared`."
3290
+ }
3291
+ },
3224
3292
  "datasources revectorize": {
3225
3293
  usage: "Usage: remy-admin datasources revectorize [--source <slug>] [settings...] [--wait]",
3226
3294
  flags: {
@@ -3491,6 +3559,7 @@ var placementOf = (a) => {
3491
3559
  const flag = a.str("placement");
3492
3560
  return flag === void 0 ? void 0 : flag === "shared" ? "shared" : { resourceId: flag };
3493
3561
  };
3562
+ var MOVE_NOTE = "Moving in the background; `datasources list` shows progress, or re-run with `datasources move --wait`.";
3494
3563
  async function dataSourcesCreate(ctx, a) {
3495
3564
  const slug = a.str("source");
3496
3565
  if (!slug) {
@@ -3499,13 +3568,14 @@ async function dataSourcesCreate(ctx, a) {
3499
3568
  const { ingest } = configFromFlags(a);
3500
3569
  const name = a.str("name");
3501
3570
  const placement = placementOf(a);
3571
+ const result = await create2(ctx, {
3572
+ slug,
3573
+ ...name ? { name } : {},
3574
+ ...ingest ? { ingest } : {},
3575
+ ...placement !== void 0 ? { placement } : {}
3576
+ });
3502
3577
  out(
3503
- await create2(ctx, {
3504
- slug,
3505
- ...name ? { name } : {},
3506
- ...ingest ? { ingest } : {},
3507
- ...placement !== void 0 ? { placement } : {}
3508
- })
3578
+ result.migration && !result.migration.error ? { ...result, note: MOVE_NOTE } : result
3509
3579
  );
3510
3580
  }
3511
3581
  async function dataSourcesConfig(ctx, a) {
@@ -3516,15 +3586,49 @@ async function dataSourcesConfig(ctx, a) {
3516
3586
  out(await configGet(ctx, slug));
3517
3587
  return;
3518
3588
  }
3589
+ const result = await configSet(ctx, {
3590
+ slug,
3591
+ ...ingest ? { ingest } : {},
3592
+ ...retrieval ? { retrieval } : {},
3593
+ ...placement !== void 0 ? { placement } : {}
3594
+ });
3519
3595
  out(
3520
- await configSet(ctx, {
3521
- slug,
3522
- ...ingest ? { ingest } : {},
3523
- ...retrieval ? { retrieval } : {},
3524
- ...placement !== void 0 ? { placement } : {}
3525
- })
3596
+ result.placementChanged && result.migration && !result.migration.error ? { ...result, note: MOVE_NOTE } : result
3526
3597
  );
3527
3598
  }
3599
+ async function dataSourcesMove(ctx, a) {
3600
+ const slug = sourceOf(a);
3601
+ const to = a.str("to");
3602
+ const placement = to === "shared" ? "shared" : { resourceId: to };
3603
+ const started = await move(ctx, { slug, placement });
3604
+ if (!started.migration || !a.bool("wait")) {
3605
+ out({
3606
+ dataSource: slug,
3607
+ ...started,
3608
+ ...started.migration ? { note: MOVE_NOTE } : {}
3609
+ });
3610
+ return;
3611
+ }
3612
+ const timeoutSec = a.num("timeout");
3613
+ const result = await waitForMove(ctx, {
3614
+ slug,
3615
+ ...timeoutSec ? { timeoutMs: timeoutSec * 1e3 } : {},
3616
+ onProgress: progress
3617
+ });
3618
+ out({
3619
+ dataSource: slug,
3620
+ status: result.status,
3621
+ placement: result.source.placement,
3622
+ migration: result.source.migration,
3623
+ ..."error" in result ? { error: result.error } : {}
3624
+ });
3625
+ if (result.status === "failed") {
3626
+ process.exit(EXIT.buildFailed);
3627
+ }
3628
+ if (result.status === "timeout") {
3629
+ process.exit(EXIT.timeout);
3630
+ }
3631
+ }
3528
3632
  async function dataSourcesRevectorize(ctx, a) {
3529
3633
  const slug = sourceOf(a);
3530
3634
  const { ingest } = configFromFlags(a);
@@ -3600,6 +3704,7 @@ var dataSourcesHandlers = {
3600
3704
  "datasources search": dataSourcesSearch,
3601
3705
  "datasources create": dataSourcesCreate,
3602
3706
  "datasources config": dataSourcesConfig,
3707
+ "datasources move": dataSourcesMove,
3603
3708
  "datasources revectorize": dataSourcesRevectorize,
3604
3709
  "datasources promote": dataSourcesPromote,
3605
3710
  "datasources drop": dataSourcesDrop,
@@ -3618,6 +3723,7 @@ Subcommands:
3618
3723
  search Query a corpus \u2014 useful to sanity-check one you just built
3619
3724
  create Create an empty source, optionally on dedicated capacity
3620
3725
  config Show or change how a corpus is processed and searched
3726
+ move Move a corpus between shared and dedicated capacity, data intact
3621
3727
  revectorize Rebuild a corpus under new settings, alongside the live one
3622
3728
  promote Make a rebuilt version live
3623
3729
  drop Discard a candidate or a superseded version
@@ -3631,6 +3737,7 @@ Usage:
3631
3737
  remy-admin datasources search [--source <slug>] [search options] <query>
3632
3738
  remy-admin datasources create --source <slug> [--name <name>] [--placement <resource-id|shared>] [rebuild settings...]
3633
3739
  remy-admin datasources config [--source <slug>] [--placement <resource-id|shared>] [settings...]
3740
+ remy-admin datasources move [--source <slug>] --to <resource-id|shared> [--wait] [--timeout <sec>]
3634
3741
  remy-admin datasources revectorize [--source <slug>] [settings...] [--wait]
3635
3742
  remy-admin datasources promote [--source <slug>] [--force]
3636
3743
  remy-admin datasources drop [--source <slug>] [--version <n>]
@@ -3701,11 +3808,23 @@ Tuning a corpus:
3701
3808
  capacity needs, roughly in proportion.
3702
3809
  --extraction-model <id>
3703
3810
 
3704
- Notes:
3811
+ Placement (shared pool vs dedicated capacity, see \`infra --help\`):
3705
3812
  --source defaults to "${DEFAULT_SOURCE}" and is created on first use, on the
3706
- shared pool. A corpus meant for dedicated capacity (\`infra provision\`)
3707
- starts with \`create --placement <resource-id>\`, since a source with built
3708
- documents cannot move; \`config --placement\` moves one that is still empty.
3813
+ shared pool. Start a corpus on a resource with \`create --placement\`, or
3814
+ move one at any time with \`move --to <resource-id|shared>\`.
3815
+ A move copies the corpus's stored vectors onto the new capacity in the
3816
+ background: no re-extraction, no re-embedding, no charge beyond the
3817
+ resource itself. Search keeps working from the old placement until the
3818
+ copy lands; add, rm, config, revectorize and delete are refused with
3819
+ data_source_migrating until then. \`datasources list\` shows progress;
3820
+ \`move --wait\` blocks on it (exits ${EXIT.buildFailed} if the move failed,
3821
+ ${EXIT.timeout} on timeout). Moving to \`shared\` is how a source leaves a
3822
+ resource you mean to destroy.
3823
+ A move is refused while documents are still building or a candidate version
3824
+ exists (data_source_busy), when the target is not active
3825
+ (capacity_<phase>), or when it would not fit (capacity_exceeded).
3826
+
3827
+ Notes:
3709
3828
  --wait blocks until processing finishes, so you can search immediately after.
3710
3829
  Exits ${EXIT.buildFailed} if a document failed, ${EXIT.timeout} on timeout.
3711
3830
  Re-adding an unchanged file is free: no upload, no re-embedding. Re-adding
@@ -5527,6 +5646,14 @@ function destroy(ctx, params) {
5527
5646
  `${base2(ctx.appId)}/${encodeURIComponent(params.id)}/destroy`
5528
5647
  );
5529
5648
  }
5649
+ function resize(ctx, params) {
5650
+ return call(
5651
+ ctx,
5652
+ "POST",
5653
+ `${base2(ctx.appId)}/${encodeURIComponent(params.id)}/resize`,
5654
+ { offeringId: params.offeringId }
5655
+ );
5656
+ }
5530
5657
  function rename(ctx, params) {
5531
5658
  return call(
5532
5659
  ctx,
@@ -5559,7 +5686,7 @@ async function waitForPhase(ctx, params) {
5559
5686
  await sleep(POLL_MS4);
5560
5687
  continue;
5561
5688
  }
5562
- if (wanted.has(resource.phase)) {
5689
+ if (wanted.has(resource.phase) && (params.until?.(resource) ?? true)) {
5563
5690
  return { status: "done", resource };
5564
5691
  }
5565
5692
  if (resource.phase === "failed") {
@@ -5577,7 +5704,7 @@ async function waitForPhase(ctx, params) {
5577
5704
  };
5578
5705
  }
5579
5706
  params.onProgress?.(
5580
- `${resource.phase}${resource.detail ? ` \xB7 ${resource.detail}` : ""}\u2026 (${Math.round((Date.now() - start) / 1e3)}s)`
5707
+ `${resource.phase}${resource.detail ? ` \xB7 ${resource.detail}` : ""}${resource.resizingTo ? ` \xB7 resizing to ${resource.resizingTo.label}` : ""}\u2026 (${Math.round((Date.now() - start) / 1e3)}s)`
5581
5708
  );
5582
5709
  await sleep(POLL_MS4);
5583
5710
  }
@@ -5626,13 +5753,27 @@ var infraSpecs = {
5626
5753
  positionals: [{ name: "id", required: true }],
5627
5754
  flags: { name: { type: "string" } },
5628
5755
  requireAnyOf: { flags: ["name"], message: "--name is required." }
5756
+ },
5757
+ "infra resize": {
5758
+ usage: "Usage: remy-admin infra resize <id> --offering <id> [--wait] [--timeout <sec>]",
5759
+ positionals: [{ name: "id", required: true }],
5760
+ flags: {
5761
+ offering: { type: "string" },
5762
+ wait: { type: "boolean" },
5763
+ timeout: { type: "string" }
5764
+ },
5765
+ requireAnyOf: {
5766
+ flags: ["offering"],
5767
+ message: "--offering is required (see `infra list` for the sizes)."
5768
+ }
5629
5769
  }
5630
5770
  };
5631
- async function waitAndReport(ctx, a, id, phases, summary2) {
5771
+ async function waitAndReport(ctx, a, id, phases, summary2, until) {
5632
5772
  const timeoutSec = a.str("timeout");
5633
5773
  const result = await waitForPhase(ctx, {
5634
5774
  id,
5635
5775
  phases,
5776
+ ...until ? { until } : {},
5636
5777
  ...timeoutSec ? { timeoutMs: Number(timeoutSec) * 1e3 } : {},
5637
5778
  onProgress: progress
5638
5779
  });
@@ -5650,6 +5791,18 @@ async function infraList(ctx, a) {
5650
5791
  async function infraGet(ctx, a) {
5651
5792
  out(await get8(ctx, { id: a.req("id") }));
5652
5793
  }
5794
+ function pricingOf(resource) {
5795
+ const offering = resource.offering;
5796
+ if (!offering) {
5797
+ return null;
5798
+ }
5799
+ return {
5800
+ offering: offering.id,
5801
+ activeMonthlyDollars: offering.monthlyPriceDollars,
5802
+ hibernatedMonthlyDollars: offering.hibernatedMonthlyPriceDollars,
5803
+ note: "Bills hourly from activation. A month's credits were verified before the lease was accepted."
5804
+ };
5805
+ }
5653
5806
  async function infraProvision(ctx, a) {
5654
5807
  const offeringId = a.str("offering");
5655
5808
  const name = a.str("name");
@@ -5657,14 +5810,19 @@ async function infraProvision(ctx, a) {
5657
5810
  offeringId,
5658
5811
  ...name ? { name } : {}
5659
5812
  });
5813
+ const pricing = pricingOf(resource);
5660
5814
  if (!a.bool("wait")) {
5661
5815
  out({
5662
5816
  resource,
5817
+ pricing,
5663
5818
  note: "Provisioning; poll `infra get <id>` or re-run with --wait."
5664
5819
  });
5665
5820
  return;
5666
5821
  }
5667
- await waitAndReport(ctx, a, resource.id, ["active"], { action: "provision" });
5822
+ await waitAndReport(ctx, a, resource.id, ["active"], {
5823
+ action: "provision",
5824
+ pricing
5825
+ });
5668
5826
  }
5669
5827
  async function infraHibernate(ctx, a) {
5670
5828
  const id = a.req("id");
@@ -5701,6 +5859,33 @@ async function infraRename(ctx, a) {
5701
5859
  })
5702
5860
  );
5703
5861
  }
5862
+ async function infraResize(ctx, a) {
5863
+ const id = a.req("id");
5864
+ const { resource } = await resize(ctx, {
5865
+ id,
5866
+ offeringId: a.str("offering")
5867
+ });
5868
+ const pricing = pricingOf({
5869
+ ...resource,
5870
+ offering: resource.resizingTo ?? resource.offering
5871
+ });
5872
+ if (!a.bool("wait")) {
5873
+ out({
5874
+ resource,
5875
+ pricing,
5876
+ note: "Resizing; searches on its sources pause while it is parked and restored. Poll `infra get <id>` or re-run with --wait."
5877
+ });
5878
+ return;
5879
+ }
5880
+ await waitAndReport(
5881
+ ctx,
5882
+ a,
5883
+ id,
5884
+ [resource.phase],
5885
+ { action: "resize", pricing },
5886
+ (r) => r.resizingTo === null
5887
+ );
5888
+ }
5704
5889
  var infraHandlers = {
5705
5890
  "infra list": infraList,
5706
5891
  "infra get": infraGet,
@@ -5708,7 +5893,8 @@ var infraHandlers = {
5708
5893
  "infra hibernate": infraHibernate,
5709
5894
  "infra resume": infraResume,
5710
5895
  "infra destroy": infraDestroy,
5711
- "infra rename": infraRename
5896
+ "infra rename": infraRename,
5897
+ "infra resize": infraResize
5712
5898
  };
5713
5899
  var infraHelp = `remy-admin infra \u2014 Dedicated infrastructure your app leases from the platform.
5714
5900
 
@@ -5717,16 +5903,18 @@ for data sources, isolated from the shared pool. Sizes and prices come from the
5717
5903
  platform catalog (\`infra list\` prints them); nothing is priced client-side.
5718
5904
 
5719
5905
  A resource bills by the hour from activation, at the retained-storage rate while
5720
- hibernated, and not at all once destroyed. Provisioning needs a month's worth of
5721
- credits available in the workspace; nothing is charged until activation. Every
5722
- action below is audited.
5906
+ hibernated, and not at all once destroyed. Provisioning (and growing) needs a
5907
+ month's worth of credits available in the workspace, checked before the request
5908
+ is accepted; nothing is charged until activation. The provision output prints
5909
+ the price it committed to. Every action below is audited.
5723
5910
 
5724
5911
  Subcommands:
5725
5912
  list Resources on this app, plus the offerings you can provision
5726
5913
  get One resource with its state timeline and attached sources
5727
- provision Lease a new resource (spends credits)
5914
+ provision Lease a new resource (needs a month's credits; prints the price)
5728
5915
  hibernate Park it: data kept, searches paused, compute charge stops
5729
5916
  resume Bring a hibernated resource back
5917
+ resize Change its size, data intact (passes through hibernated)
5730
5918
  destroy Delete it (refused while data sources are placed on it)
5731
5919
  rename Change the display name
5732
5920
 
@@ -5736,26 +5924,44 @@ Usage:
5736
5924
  remy-admin infra provision --offering <id> [--name <name>] [--wait] [--timeout <sec>]
5737
5925
  remy-admin infra hibernate <id> [--wait] [--timeout <sec>]
5738
5926
  remy-admin infra resume <id> [--wait] [--timeout <sec>]
5927
+ remy-admin infra resize <id> --offering <id> [--wait] [--timeout <sec>]
5739
5928
  remy-admin infra destroy <id> [--wait] [--timeout <sec>]
5740
5929
  remy-admin infra rename <id> --name <name>
5741
5930
 
5742
5931
  Placing a data source on a resource:
5743
5932
  remy-admin datasources create --source <slug> --placement <resource-id>
5744
- remy-admin datasources config --source <slug> --placement <resource-id>
5745
- remy-admin datasources config --source <slug> --placement shared
5933
+ remy-admin datasources move --source <slug> --to <resource-id|shared> [--wait]
5746
5934
 
5747
5935
  Start a new corpus on a resource with \`create\`; \`add\` creates a source on
5748
- the shared pool. Placement can only change while the source has no built
5749
- documents; moving a populated source is a re-vectorization onto the new
5750
- capacity, which is not available yet. Searches on a source whose resource is
5751
- not active fail with capacity_<phase>: capacity_hibernated once parked,
5752
- capacity_hibernating / capacity_resuming while it moves, and
5753
- capacity_restoring while a resumed instance reloads its data.
5754
-
5755
- Phases:
5936
+ the shared pool. A populated source moves with \`datasources move\`: its
5937
+ stored vectors are copied onto the new capacity in the background (no
5938
+ re-embedding), search keeps working from the old placement until the copy
5939
+ lands, and adding or removing documents is refused with
5940
+ data_source_migrating until it finishes. Moving to \`shared\` is how a source
5941
+ leaves a resource you mean to destroy. See \`datasources --help\`.
5942
+
5943
+ Searches on a source whose resource is not active fail with
5944
+ capacity_<phase>: capacity_hibernated once parked, capacity_hibernating /
5945
+ capacity_resuming while it moves. \`active\` means searchable: a resumed
5946
+ resource reports active only once its data is restored. capacity_restoring
5947
+ is the rare case of an active instance that was replaced (node loss) and is
5948
+ refilling from its snapshot. The messages are written for the app's end
5949
+ user; the stable code is what to act on.
5950
+
5951
+ Resizing:
5952
+ A resize keeps the data. The resource is parked (a fresh snapshot), its spec
5953
+ is swapped, and it comes back up restored from that snapshot, so searches on
5954
+ its sources pause for the few minutes it takes. A hibernated resource swaps
5955
+ in place and stays parked. Growing needs a month's credits at the new rate;
5956
+ shrinking is refused below what the resource holds (resize_too_small).
5957
+ \`infra get\` shows resizingTo until the swap has happened.
5958
+
5959
+ Phases and timing:
5756
5960
  requested \u2192 provisioning \u2192 active \u21C4 hibernated \u2192 destroyed, with hibernating /
5757
5961
  resuming / decommissioning in between and failed when a change cannot complete
5758
- (retried automatically; resume or hibernate to retry by hand).
5962
+ (retried automatically; resume or hibernate to retry by hand). Each transition
5963
+ takes about two minutes, more with more data: snapshots and restores scale
5964
+ with the collection. --wait defaults to 600s; pass --timeout for a large one.
5759
5965
 
5760
5966
  --wait exit codes: 0 reached the target phase \xB7 1 the platform reported failure \xB7
5761
5967
  2 still transitioning when --timeout elapsed (default 600s).`;
@@ -5916,6 +6122,7 @@ Commands:
5916
6122
  settings App settings \u2014 signup allowlist, test accounts, embed origins, toggles
5917
6123
  email Email \u2014 delivery log, blast stats, unsubscribes, inbox, custom domains
5918
6124
  cron Scheduled jobs \u2014 schedules, run history, manual trigger
6125
+ infra Dedicated infrastructure \u2014 provision, hibernate, resize + destroy retrieval capacity
5919
6126
 
5920
6127
  Run 'remy-admin <command> --help' for details on each command.
5921
6128
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@madewithremy/admin",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Admin SDK + CLI for managing production Remy apps",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",