@shopify/cli-kit
Version:
A set of utilities, interfaces, and models that are common across all the platform features
123 lines • 5.76 kB
JavaScript
import { isUnitTest } from './context/local.js';
import { performance } from 'node:perf_hooks';
/**
* Get the error handling strategy for metadata.
*
* @returns 'mute-and-report' in production, 'bubble' in tests.
*/
function getMetadataErrorHandlingStrategy() {
if (isUnitTest()) {
return 'bubble';
}
return 'mute-and-report';
}
/**
* Creates a container for metadata collected at runtime.
* The container provides async-safe functions for extracting the gathered metadata, and for setting it.
*
* @param defaultPublicMetadata - Optional, default data for the container.
* @returns A container for the metadata.
*/
export function createRuntimeMetadataContainer(defaultPublicMetadata = {}) {
const raw = {
sensitive: {},
public: {
...defaultPublicMetadata,
},
};
const addPublic = (data) => {
Object.assign(raw.public, data);
};
const addSensitive = (data) => {
Object.assign(raw.sensitive, data);
};
const addMetadata = async (addFn, getFn, onError) => {
const errorHandling = onError === 'auto' ? getMetadataErrorHandlingStrategy() : onError;
const getAndSet = async () => {
const data = await getFn();
addFn(data);
};
if (errorHandling === 'bubble') {
await getAndSet();
}
else {
try {
await getAndSet();
// eslint-disable-next-line no-catch-all/no-catch-all, @typescript-eslint/no-explicit-any
}
catch (error) {
// This is very prone to becoming a circular dependency, so we import it dynamically
const { sendErrorToBugsnag } = await import('./error-handler.js');
await sendErrorToBugsnag(error, 'unexpected_error');
}
}
};
// See `runWithTimer` below.
const durationStack = [];
return {
getAllPublicMetadata: () => {
return { ...raw.public };
},
getAllSensitiveMetadata: () => {
return { ...raw.sensitive };
},
addPublicMetadata: async (getData, onError = 'auto') => {
return addMetadata(addPublic, getData, onError);
},
addSensitiveMetadata: async (getData, onError = 'auto') => {
return addMetadata(addSensitive, getData, onError);
},
runWithTimer: (field) => {
return async (fn) => {
/**
* For nested timers, we subtract the inner timer's duration from the outer timer's. We use a stack to track the
* cumulative durations of nested timers. On starting a timer, we push a zero onto the stack to initialize the total
* duration for subsequent nested timers. Before logging, we pop the stack to get the total nested timers' duration.
* We subtract this from the current timer's actual duration to get its measurable duration. We then add the current
* timer's actual duration to the stack's top, allowing any parent timer to deduct it from its own duration.
*/
// Initialise the running total duration for all nested timers
durationStack.push(0);
// Do the work, and time it
const start = performance.now();
try {
const result = await fn();
return result;
}
finally {
let end = performance.now();
// For very short durations, the end time can be before the start time(!) - we flatten this out to zero.
end = Math.max(start, end);
// The top of the stack is the total time for all nested timers
const wallClockDuration = Math.max(end - start, 0);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const childDurations = durationStack.pop();
const duration = Math.max(wallClockDuration - childDurations, 0);
// If this is the topmost timer, the stack will be empty.
if (durationStack.length > 0) {
durationStack[durationStack.length - 1] = (durationStack[durationStack.length - 1] ?? 0) + wallClockDuration;
}
// Log it -- we include it in the metadata, but also log via the standard performance API. The TS types for this library are not quite right, so we have to cast to `any` here.
performance.measure(`${field}#measurable`, {
start,
duration,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
});
performance.measure(`${field}#wall`, {
start,
end,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
});
// There might not be a value set, yet
let currentValue = (raw.public[field] || 0);
currentValue += duration;
// TS is not quite smart enough to realise that raw.public[field] must be a numeric type
raw.public[field] = currentValue;
}
};
},
};
}
const coreData = createRuntimeMetadataContainer({ cmd_all_timing_network_ms: 0, cmd_all_timing_prompts_ms: 0 });
export const { getAllPublicMetadata, getAllSensitiveMetadata, addPublicMetadata, addSensitiveMetadata, runWithTimer } = coreData;
//# sourceMappingURL=metadata.js.map