sliftutils 1.7.118 → 1.7.119

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.
@@ -188,11 +188,9 @@ async function maintainIntermediates(): Promise<void> {
188
188
  let key = `${account}/${bucketName}`;
189
189
  let newest = await readNewestRoutingFile(account, bucketName);
190
190
  if (!newest) continue;
191
- let routing: RemoteConfig;
192
- try {
193
- routing = parseRoutingData(newest.data);
194
- } catch (e) {
195
- console.error(`Skipping switchover maintenance for bucket ${key}: its routing config could not be parsed (${(e as Error).message})`);
191
+ let routing = parseRoutingData(newest.data);
192
+ if (!routing) {
193
+ console.error(`Skipping switchover maintenance for bucket ${key}: its routing config could not be parsed`);
196
194
  continue;
197
195
  }
198
196
  let expired = expireIntermediateSources(routing, Date.now());
@@ -60,5 +60,6 @@ export declare function assertValidRemoteConfig(config: RemoteConfig): void;
60
60
  export declare function sourceIdentity(sourceConfig: SourceConfig | undefined): string;
61
61
  /** What an index entry records as the holder of its bytes (see ArchivesSource.url), so it must name the endpoint FOREVER. An intermediate is a switchover's temporary alternate port onto another source, and that port is gone for good once its window passes - so it is recorded as the source it was split out of, which holds the same bucket and outlives it. */
62
62
  export declare function sourcePersistentUrl(sourceConfig: SourceConfig | undefined, folder: string): string;
63
- export declare function parseRoutingData(data: Buffer): RemoteConfig;
63
+ /** Reads a stored routing config. NEVER throws: this runs on every READ of a stored config, and a torn/corrupt file must not brick the paths that would fix it - above all writeRoutingConfig, where throwing while reading the OLD config blocks the write of the NEW one forever. Unreadable data is logged and read as undefined - the same as the file not existing. Judging a config on its way IN is the writer's job (see assertValidRemoteConfig at the write entry points), where rejecting bad data with a throw is correct. */
64
+ export declare function parseRoutingData(data: Buffer): RemoteConfig | undefined;
64
65
  export declare function serializeRemoteConfig(config: RemoteConfig): Buffer;
@@ -288,16 +288,19 @@ function storeIdentity(source: SourceConfig): string {
288
288
  return `${source.intermediate || source.url}|${source.name}`;
289
289
  }
290
290
 
291
- export function parseRoutingData(data: Buffer): RemoteConfig {
291
+ /** Reads a stored routing config. NEVER throws: this runs on every READ of a stored config, and a torn/corrupt file must not brick the paths that would fix it - above all writeRoutingConfig, where throwing while reading the OLD config blocks the write of the NEW one forever. Unreadable data is logged and read as undefined - the same as the file not existing. Judging a config on its way IN is the writer's job (see assertValidRemoteConfig at the write entry points), where rejecting bad data with a throw is correct. */
292
+ export function parseRoutingData(data: Buffer): RemoteConfig | undefined {
292
293
  let text = data.toString();
293
294
  let parsed: RemoteConfig;
294
295
  try {
295
296
  parsed = JSON.parse(text) as RemoteConfig;
296
297
  } catch (e) {
297
- throw new Error(`Routing config is not valid JSON (${String(e)}). Data: ${text.slice(0, 500)}`);
298
+ console.error(`Ignoring a stored routing config - it is not valid JSON (${String(e)}). Data: ${JSON.stringify(text).slice(0, 500)}`);
299
+ return undefined;
298
300
  }
299
301
  if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.sources)) {
300
- throw new Error(`Routing config must be { version?, sources: [...] }, was ${text.slice(0, 500)}`);
302
+ console.error(`Ignoring a stored routing config - it must be { version?, sources: [...] }, was ${text.slice(0, 500)}`);
303
+ return undefined;
301
304
  }
302
305
  return normalizeRemoteConfig(parsed);
303
306
  }
@@ -193,6 +193,9 @@ export async function writeRoutingConfig(account: string, bucketName: string, na
193
193
  assertWritesAllowed();
194
194
  let store = getStore(account, bucketName, name);
195
195
  let incoming = parseRoutingData(data);
196
+ if (!incoming) {
197
+ throw new Error(`Routing config write rejected - not a parseable config. The data written for ${account}/${bucketName} (store ${JSON.stringify(name)}) is not a valid { version?, sources: [...] } JSON config (${data.length} bytes)`);
198
+ }
196
199
  let current = await readRoutingFromDisk(account, bucketName);
197
200
  let stored = reinjectIntermediates(current, incoming);
198
201
  let storedData = Buffer.from(serializeRemoteConfig(stored));
@@ -127,7 +127,9 @@ export class StoreSync {
127
127
  try {
128
128
  let theirs = await source.get2(ROUTING_FILE, { internal: true });
129
129
  if (!theirs || !theirs.data || !theirs.data.length) continue;
130
- let version = getConfigVersion(parseRoutingData(theirs.data));
130
+ let theirRouting = parseRoutingData(theirs.data);
131
+ if (!theirRouting) continue;
132
+ let version = getConfigVersion(theirRouting);
131
133
  if (version <= this.store.routingVersion()) continue;
132
134
  console.log(`Routing config version ${version} found on ${source.getDebugName()} (we have ${this.store.routingVersion()}, store ${this.store.folder}): taking it`);
133
135
  // Stamped NOW, not with their write time: configs are ordered by version, and this one is newer by that ordering. Their file can easily be older by write time (ours was rewritten locally more recently), and the store's only-take-the-latest rule would then drop it - leaving us finding the same newer version on every poll and never adopting it.