fluncle 0.244.0 → 0.246.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.
Files changed (2) hide show
  1. package/bin/fluncle.mjs +118 -12
  2. package/package.json +1 -1
package/bin/fluncle.mjs CHANGED
@@ -622,7 +622,7 @@ function parseVersion(version) {
622
622
  var currentVersion;
623
623
  var init_version = __esm(() => {
624
624
  init_output();
625
- currentVersion = "0.244.0".trim() ? "0.244.0".trim() : "0.1.0";
625
+ currentVersion = "0.246.0".trim() ? "0.246.0".trim() : "0.1.0";
626
626
  });
627
627
 
628
628
  // src/update-notifier.ts
@@ -2335,7 +2335,7 @@ function parseVersion2(version) {
2335
2335
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2336
2336
  var init_version2 = __esm(() => {
2337
2337
  init_output();
2338
- currentVersion2 = "0.244.0".trim() ? "0.244.0".trim() : "0.1.0";
2338
+ currentVersion2 = "0.246.0".trim() ? "0.246.0".trim() : "0.1.0";
2339
2339
  });
2340
2340
 
2341
2341
  // ../../packages/registry/src/index.ts
@@ -12341,7 +12341,7 @@ var init_admin_prompts = __esm(() => {
12341
12341
  });
12342
12342
 
12343
12343
  // ../../packages/contracts/src/orpc/admin-projections.ts
12344
- var PROJECTION_STEP_LIMIT_MAX = 500, ProjectionTargetSchema, ProjectionCutoverSchema, ProjectionStepActionSchema, CountSchema, BoundedCountSchema, DigestSchema2, RebuildSchema, BacklogSchema, RepairSchema, OldestOutstandingMarkerAgeSchema, ConvergenceSchema, ProjectionFamilyStatusSchema, ProjectionStatusSchema, getProjectionStatus, advanceProjection, setProjectionCutover, adminProjectionsContract;
12344
+ var PROJECTION_STEP_LIMIT_MAX = 500, ProjectionTargetSchema, ProjectionCutoverSchema, ProjectionStepActionSchema, CountSchema, BoundedCountSchema, DigestSchema2, RebuildSchema, BacklogSchema, RepairSchema, OldestOutstandingMarkerAgeSchema, ConvergenceSchema, ProjectionFamilyStatusSchema, ProjectionStatusSchema, getProjectionStatus, advanceProjection, setProjectionCutover, DUE_WORK_REKEY_LIMIT_MAX = 500, rekeyDueWorkQueue, adminProjectionsContract;
12345
12345
  var init_admin_projections = __esm(() => {
12346
12346
  init_dist3();
12347
12347
  init_zod();
@@ -12400,7 +12400,9 @@ var init_admin_projections = __esm(() => {
12400
12400
  artistQualification: ProjectionFamilyStatusSchema,
12401
12401
  crawlDueWork: ProjectionFamilyStatusSchema,
12402
12402
  publicAggregates: ProjectionFamilyStatusSchema.extend({ anchorsReady: boolean2() }),
12403
- trackDueWork: ProjectionFamilyStatusSchema
12403
+ trackDueWork: ProjectionFamilyStatusSchema.extend({
12404
+ catalogueRankMarkerAgeMs: CountSchema.nullable().optional()
12405
+ })
12404
12406
  }),
12405
12407
  readyToOpen: object({
12406
12408
  crawlDueWork: boolean2(),
@@ -12431,6 +12433,8 @@ var init_admin_projections = __esm(() => {
12431
12433
  complete: boolean2(),
12432
12434
  ok: literal(true),
12433
12435
  processed: CountSchema,
12436
+ rebuildRowsWalked: CountSchema.optional(),
12437
+ rebuildStaleFamilies: CountSchema.optional(),
12434
12438
  scheduled: CountSchema,
12435
12439
  status: ProjectionStatusSchema.optional(),
12436
12440
  target: ProjectionTargetSchema,
@@ -12448,9 +12452,33 @@ var init_admin_projections = __esm(() => {
12448
12452
  status: ProjectionStatusSchema,
12449
12453
  target: ProjectionCutoverSchema
12450
12454
  }));
12455
+ rekeyDueWorkQueue = oc.route({
12456
+ method: "POST",
12457
+ operationId: "rekeyDueWorkQueue",
12458
+ path: "/admin/projections/due-work/{workKind}/rekey",
12459
+ summary: "Mark one due-work queue for re-keying under today's order definition",
12460
+ tags: ["Admin"]
12461
+ }).input(object({
12462
+ apply: boolean2().default(false),
12463
+ cursor: string2().max(200).nullable().default(null),
12464
+ limit: number2().int().min(1).max(DUE_WORK_REKEY_LIMIT_MAX).default(DUE_WORK_REKEY_LIMIT_MAX),
12465
+ workKind: string2().min(1).max(64)
12466
+ })).output(object({
12467
+ applied: boolean2(),
12468
+ cursor: string2().nullable(),
12469
+ definitionVersion: string2(),
12470
+ hasMore: boolean2(),
12471
+ marked: CountSchema,
12472
+ matched: CountSchema,
12473
+ ok: literal(true),
12474
+ remaining: BoundedCountSchema,
12475
+ subjectType: string2(),
12476
+ workKind: string2()
12477
+ }));
12451
12478
  adminProjectionsContract = {
12452
12479
  advance_projection: advanceProjection,
12453
12480
  get_projection_status: getProjectionStatus,
12481
+ rekey_due_work_queue: rekeyDueWorkQueue,
12454
12482
  set_projection_cutover: setProjectionCutover
12455
12483
  };
12456
12484
  });
@@ -16842,32 +16870,84 @@ async function getProjectionStatusCommand() {
16842
16870
  return adminApiGet("/api/v1/admin/projections/status");
16843
16871
  }
16844
16872
  async function advanceProjectionCommand(input) {
16845
- const { includeTerminalStatus = true, maxSteps = 1, target, ...body } = input;
16873
+ const {
16874
+ includeTerminalStatus = true,
16875
+ maxSteps = 1,
16876
+ now = () => Date.now(),
16877
+ target,
16878
+ wallMs,
16879
+ ...body
16880
+ } = input;
16846
16881
  if (!includeTerminalStatus && input.action !== "repair") {
16847
16882
  throw new Error("terminal status may be omitted only for repair automation");
16848
16883
  }
16849
16884
  const stepBody = maxSteps > 1 || !includeTerminalStatus ? { ...body, includeStatus: false } : body;
16885
+ const startedAt = now();
16850
16886
  let response = await adminApiPost(`/api/v1/admin/projections/${target}/advance`, stepBody);
16851
16887
  let steps = 1;
16852
16888
  let processed = response.processed;
16853
16889
  let scheduled = response.scheduled;
16890
+ let wallStopped = false;
16891
+ let rebuildRowsWalked = response.rebuildRowsWalked ?? 0;
16854
16892
  while (!response.complete && steps < maxSteps) {
16893
+ if (wallMs !== undefined && now() - startedAt >= wallMs) {
16894
+ wallStopped = true;
16895
+ break;
16896
+ }
16855
16897
  response = await adminApiPost(`/api/v1/admin/projections/${target}/advance`, stepBody);
16856
16898
  steps += 1;
16857
16899
  processed += response.processed;
16858
16900
  scheduled += response.scheduled;
16901
+ rebuildRowsWalked += response.rebuildRowsWalked ?? 0;
16859
16902
  }
16903
+ const rebuild = response.rebuildStaleFamilies === undefined ? {} : { rebuildRowsWalked, rebuildStaleFamilies: response.rebuildStaleFamilies };
16860
16904
  if (!includeTerminalStatus) {
16861
16905
  const { status: _status, ...withoutStatus } = response;
16862
- return { ...withoutStatus, processed, scheduled, steps };
16906
+ return { ...withoutStatus, ...rebuild, processed, scheduled, steps, wallStopped };
16863
16907
  }
16864
16908
  const status = response.status ?? (await getProjectionStatusCommand()).status;
16865
- return { ...response, processed, scheduled, status, steps };
16909
+ return { ...response, ...rebuild, processed, scheduled, status, steps, wallStopped };
16866
16910
  }
16867
16911
  async function setProjectionCutoverCommand(input) {
16868
16912
  const { enabled, target } = input;
16869
16913
  return adminApiPut(`/api/v1/admin/projections/${target}/cutover`, { enabled });
16870
16914
  }
16915
+ async function rekeyDueWorkQueueCommand(input) {
16916
+ const { apply, limit, maxPages, workKind } = input;
16917
+ let cursor = input.cursor;
16918
+ let response = await adminApiPost(`/api/v1/admin/projections/due-work/${encodeURIComponent(workKind)}/rekey`, { apply, cursor, limit });
16919
+ let pages = 1;
16920
+ let marked = response.marked;
16921
+ let matched = response.matched;
16922
+ cursor = response.cursor;
16923
+ while (apply && response.hasMore && response.cursor !== null && pages < maxPages) {
16924
+ response = await adminApiPost(`/api/v1/admin/projections/due-work/${encodeURIComponent(workKind)}/rekey`, { apply, cursor, limit });
16925
+ pages += 1;
16926
+ marked += response.marked;
16927
+ matched += response.matched;
16928
+ cursor = response.cursor;
16929
+ }
16930
+ return { ...response, cursor, marked, matched, pages };
16931
+ }
16932
+ function parseDueWorkRekeyLimit(value) {
16933
+ const limit = Number(value);
16934
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > DUE_WORK_REKEY_LIMIT_MAX) {
16935
+ throw new Error(`--limit must be a whole number from 1 through ${DUE_WORK_REKEY_LIMIT_MAX}`);
16936
+ }
16937
+ return limit;
16938
+ }
16939
+ function parseDueWorkRekeyMaxPages(value) {
16940
+ const pages = Number(value);
16941
+ if (!Number.isSafeInteger(pages) || pages < 1 || pages > DUE_WORK_REKEY_MAX_PAGES) {
16942
+ throw new Error(`--max-pages must be a whole number from 1 through ${DUE_WORK_REKEY_MAX_PAGES}`);
16943
+ }
16944
+ return pages;
16945
+ }
16946
+ function dueWorkRekeyLine(result) {
16947
+ const remaining = `${result.remaining.count}${result.remaining.truncated ? "+" : ""}`;
16948
+ const verb = result.applied ? `marked ${result.marked}` : `would mark ${result.matched}`;
16949
+ return `${result.workKind} re-key — ${verb} over ${result.pages} page(s); ${remaining} still on the old key; cursor ${result.cursor ?? "start"}; definition ${result.definitionVersion}.`;
16950
+ }
16871
16951
  function parseProjectionEnabled(value) {
16872
16952
  if (value !== "true" && value !== "false") {
16873
16953
  throw new Error("--enabled must be true or false");
@@ -16888,6 +16968,13 @@ function parseProjectionMaxSteps(value) {
16888
16968
  }
16889
16969
  return maxSteps;
16890
16970
  }
16971
+ function parseProjectionWallMs(value) {
16972
+ const wallMs = Number(value);
16973
+ if (!Number.isSafeInteger(wallMs) || wallMs < PROJECTION_WALL_MS_MIN || wallMs > PROJECTION_WALL_MS_MAX) {
16974
+ throw new Error(`--wall-ms must be a whole number of milliseconds from ${PROJECTION_WALL_MS_MIN} through ${PROJECTION_WALL_MS_MAX}`);
16975
+ }
16976
+ return wallMs;
16977
+ }
16891
16978
  function parseProjectionTarget(value) {
16892
16979
  if (value !== "artist_qualification" && value !== "crawl_due_work" && value !== "public_aggregates" && value !== "track_due_work") {
16893
16980
  throw new Error("--target must be artist_qualification, crawl_due_work, public_aggregates, or track_due_work");
@@ -16915,7 +17002,7 @@ function projectionStatusLines(status) {
16915
17002
  row("Artist qualification", status.projections.artistQualification, status.cutovers.publicProjections)
16916
17003
  ];
16917
17004
  }
16918
- var PROJECTION_MAX_STEPS = 100;
17005
+ var PROJECTION_MAX_STEPS = 100, DUE_WORK_REKEY_MAX_PAGES = 200, PROJECTION_WALL_MS_MIN = 1000, PROJECTION_WALL_MS_MAX = 600000;
16919
17006
  var init_admin_projections2 = __esm(() => {
16920
17007
  init_orpc();
16921
17008
  init_api();
@@ -21744,7 +21831,7 @@ JSON field reference:
21744
21831
  console.log(projectionStatusLines(result.status).join(`
21745
21832
  `));
21746
21833
  });
21747
- adminProjections.command("advance").description("Run bounded, resumable rebuild, repair, or audit steps").requiredOption("--target <target>", "track_due_work, crawl_due_work, public_aggregates, or artist_qualification").requiredOption("--action <action>", "rebuild, repair, or audit").option("--limit <limit>", "Maximum rows or repairs in this request (1-500)", "100").option("--max-steps <steps>", "Maximum sequential advance calls (1-100)").option("--no-terminal-status", "Omit the terminal readiness snapshot (JSON repair automation only)").option("--json", "Print the aggregate result and default readiness as JSON", false).action(async (options) => {
21834
+ adminProjections.command("advance").description("Run bounded, resumable rebuild, repair, or audit steps").requiredOption("--target <target>", "track_due_work, crawl_due_work, public_aggregates, or artist_qualification").requiredOption("--action <action>", "rebuild, repair, or audit").option("--limit <limit>", "Maximum rows or repairs in this request (1-500)", "100").option("--max-steps <steps>", "Maximum sequential advance calls (1-100)").option("--wall-ms <ms>", "Stop issuing further steps once this many milliseconds have elapsed (1000-600000)").option("--no-terminal-status", "Omit the terminal readiness snapshot (JSON repair automation only)").option("--json", "Print the aggregate result and default readiness as JSON", false).action(async (options) => {
21748
21835
  const projections = await Promise.resolve().then(() => (init_admin_projections2(), {}));
21749
21836
  const action = parseProjectionAction(options.action);
21750
21837
  if (!options.terminalStatus && (!options.json || action !== "repair")) {
@@ -21755,13 +21842,29 @@ JSON field reference:
21755
21842
  includeTerminalStatus: options.terminalStatus,
21756
21843
  limit: parseProjectionLimit(options.limit),
21757
21844
  maxSteps: options.maxSteps === undefined ? undefined : parseProjectionMaxSteps(options.maxSteps),
21758
- target: parseProjectionTarget(options.target)
21845
+ target: parseProjectionTarget(options.target),
21846
+ wallMs: options.wallMs === undefined ? undefined : parseProjectionWallMs(options.wallMs)
21847
+ });
21848
+ if (options.json) {
21849
+ printJson(result);
21850
+ return;
21851
+ }
21852
+ console.log(`${result.target} ${result.action}: steps ${result.steps}, processed ${result.processed}, scheduled ${result.scheduled}, ${result.complete ? "complete" : result.wallStopped ? "wall budget spent, more work remains" : "more work remains"}.`);
21853
+ });
21854
+ adminProjections.command("rekey").description("Mark one due-work queue for re-keying under today's order definition").requiredOption("--work-kind <kind>", "Physical queue, e.g. capture-catalogue").option("--cursor <subjectId>", "Resume after this subject id").option("--limit <limit>", "Rows per page (1-500)", "500").option("--max-pages <pages>", "Maximum pages to mark in one run (1-200)", "1").option("--apply", "Write the repair markers; omit for a dry run", false).option("--json", "Print the page result as JSON", false).action(async (options) => {
21855
+ const projections = await Promise.resolve().then(() => (init_admin_projections2(), {}));
21856
+ const result = await rekeyDueWorkQueueCommand({
21857
+ apply: options.apply,
21858
+ cursor: options.cursor ?? null,
21859
+ limit: parseDueWorkRekeyLimit(options.limit),
21860
+ maxPages: parseDueWorkRekeyMaxPages(options.maxPages),
21861
+ workKind: options.workKind
21759
21862
  });
21760
21863
  if (options.json) {
21761
21864
  printJson(result);
21762
21865
  return;
21763
21866
  }
21764
- console.log(`${result.target} ${result.action}: steps ${result.steps}, processed ${result.processed}, scheduled ${result.scheduled}, ${result.complete ? "complete" : "more work remains"}.`);
21867
+ console.log(dueWorkRekeyLine(result));
21765
21868
  });
21766
21869
  adminProjections.command("set").description("Open or close one readiness-gated projection cutover").requiredOption("--target <target>", "track_due_work, crawl_due_work, or public_projections").requiredOption("--enabled <boolean>", "true opens; false closes unconditionally").option("--json", "Print the stored flag and current readiness as JSON", false).action(async (options) => {
21767
21870
  const projections = await Promise.resolve().then(() => (init_admin_projections2(), {}));
@@ -25762,6 +25865,7 @@ var stringOptions = new Set([
25762
25865
  "--lens",
25763
25866
  "--limit",
25764
25867
  "--max-hop",
25868
+ "--max-pages",
25765
25869
  "--max-steps",
25766
25870
  "--max-overlap",
25767
25871
  "--metrics",
@@ -25820,8 +25924,10 @@ var stringOptions = new Set([
25820
25924
  "--video-url",
25821
25925
  "--view",
25822
25926
  "--voice-id",
25927
+ "--wall-ms",
25823
25928
  "--window-since",
25824
- "--window-until"
25929
+ "--window-until",
25930
+ "--work-kind"
25825
25931
  ]);
25826
25932
  var rootHelpSections = `
25827
25933
 
package/package.json CHANGED
@@ -31,5 +31,5 @@
31
31
  "url": "git+https://github.com/mauricekleine/fluncle.git"
32
32
  },
33
33
  "type": "module",
34
- "version": "0.244.0"
34
+ "version": "0.246.0"
35
35
  }