mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-26 06:34:20 +08:00
190 lines
10 KiB
JavaScript
190 lines
10 KiB
JavaScript
import dc, { Channel, channel, tracingChannel } from "node:diagnostics_channel";
|
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
|
const show = (label, ...values) => console.log(label + " " + JSON.stringify(values));
|
|
const events = ["start", "end", "asyncStart", "asyncEnd", "error"];
|
|
const record = (trace, output) => {
|
|
const handlers = Object.fromEntries(events.map((event) => [event, (context, name) => {
|
|
output.push([event, context.tag, context.result, context.error?.message, name === trace[event].name]);
|
|
}]));
|
|
trace.subscribe(handlers);
|
|
return handlers;
|
|
};
|
|
const errorShape = (fn) => {
|
|
try { fn(); return ["did-not-throw"]; }
|
|
catch (error) { return [error.name, error.code, error.message]; }
|
|
};
|
|
|
|
export function report() {
|
|
const symbol = Symbol("private-channel");
|
|
const ch = channel(symbol);
|
|
show("identity", channel(symbol) === ch, channel(Symbol("private-channel")) !== ch,
|
|
ch instanceof Channel, ch.name === symbol, dc.Channel === Channel,
|
|
channel("__proto__") === channel("__proto__"), dc.hasSubscribers(42));
|
|
const seen = [];
|
|
const late = (data, name) => seen.push(["late", data, name === symbol]);
|
|
const removed = (data) => seen.push(["removed", data]);
|
|
const first = (data) => { seen.push(["first", data]); ch.unsubscribe(removed); ch.subscribe(late); };
|
|
ch.subscribe(first);
|
|
ch.subscribe(removed);
|
|
ch.publish(1);
|
|
ch.publish(2);
|
|
show("publish-snapshot", seen, ch.hasSubscribers, dc.hasSubscribers(symbol));
|
|
show("unsubscribe", ch.unsubscribe(first), ch.unsubscribe(first), ch.unsubscribe(late),
|
|
ch.hasSubscribers, ch.unsubscribe(late), ch.hasSubscribers);
|
|
const duplicate = channel("duplicate");
|
|
const repeated = () => seen.push("duplicate");
|
|
duplicate.subscribe(repeated);
|
|
duplicate.subscribe(repeated);
|
|
show("duplicate", duplicate.unsubscribe(repeated), duplicate.hasSubscribers,
|
|
duplicate.unsubscribe(repeated), duplicate.hasSubscribers, duplicate.unsubscribe(repeated));
|
|
|
|
const trace = tracingChannel("sync-fixture");
|
|
show("trace-shape", trace.constructor.name, events.map((event) => trace[event].name),
|
|
events.every((event) => trace[event] === channel("tracing:sync-fixture:" + event)),
|
|
events.map((event) => {
|
|
const descriptor = Object.getOwnPropertyDescriptor(trace, event);
|
|
return [descriptor.writable, descriptor.enumerable, descriptor.configurable, descriptor.value === trace[event]];
|
|
}), trace.hasSubscribers);
|
|
const output = [];
|
|
const handlers = record(trace, output);
|
|
const context = { tag: "success" };
|
|
const receiver = { base: 10 };
|
|
const result = trace.traceSync(function (a, b) { return this.base + a + b; }, context, receiver, 2, 3);
|
|
show("sync", result, context.result === result, output.splice(0));
|
|
const objectResult = { value: "same object" };
|
|
const identityContext = { tag: "identity" };
|
|
show("sync-identity", trace.traceSync(() => objectResult, identityContext) === objectResult,
|
|
identityContext.result === objectResult, output.splice(0));
|
|
const boom = new Error("sync failure");
|
|
const failed = { tag: "failure" };
|
|
try { trace.traceSync(() => { throw boom; }, failed); }
|
|
catch (error) { show("sync-error", error === boom, failed.error === boom, output.splice(0)); }
|
|
trace.traceSync(() => "default context");
|
|
show("default-context", output.splice(0));
|
|
show("trace-unsubscribe", trace.unsubscribe(handlers), trace.unsubscribe(handlers),
|
|
trace.unsubscribe({}), trace.hasSubscribers);
|
|
const silent = {};
|
|
const promised = Promise.resolve(5);
|
|
show("silent", trace.traceSync(() => 8, silent), Object.keys(silent),
|
|
trace.tracePromise(() => promised, silent) === promised,
|
|
trace.traceCallback((value) => value, 0, silent, null, "not a callback"));
|
|
const explicit = tracingChannel(Object.fromEntries(events.map((event) => [event, channel("explicit:" + event)])));
|
|
const cloned = tracingChannel(explicit);
|
|
show("explicit", events.every((event) => explicit[event] === cloned[event]), explicit.hasSubscribers);
|
|
for (const value of [undefined, null, 42, false, {}, Symbol("bad")]) {
|
|
show("channel-error", errorShape(() => channel(value)));
|
|
}
|
|
for (const value of [undefined, null, 42, false, Symbol("bad")]) {
|
|
show("tracing-error", errorShape(() => tracingChannel(value)));
|
|
}
|
|
show("collection-error", errorShape(() => tracingChannel({ start: {} })));
|
|
const invalid = channel("invalid-subscription");
|
|
show("subscription-error", errorShape(() => invalid.subscribe(42)), invalid.hasSubscribers);
|
|
explicit.subscribe({ start: () => {} });
|
|
show("callback-error", errorShape(() => explicit.traceCallback(() => {}, 0, {}, null, "bad")));
|
|
|
|
const bound = channel("bound-stores");
|
|
const direct = new AsyncLocalStorage();
|
|
const shaped = new AsyncLocalStorage();
|
|
const transforms = [];
|
|
bound.bindStore(direct);
|
|
bound.bindStore(shaped, (data) => { transforms.push([data.value, direct.getStore()?.value ?? null]); return { wrapped: data.value }; });
|
|
show("bound-active", bound.hasSubscribers, dc.hasSubscribers(bound.name));
|
|
bound.subscribe((data) => show("stored-subscriber", direct.getStore() === data, shaped.getStore()));
|
|
const data = { value: "outer" };
|
|
const storedResult = bound.runStores(data, function (a, b) {
|
|
show("stores", direct.getStore() === data, shaped.getStore(), this.name, a, b);
|
|
bound.runStores({ value: "inner" }, () => show("nested-store", shaped.getStore()));
|
|
show("restored-store", direct.getStore() === data, shaped.getStore());
|
|
return "stored result";
|
|
}, { name: "receiver" }, 7, 8);
|
|
show("stores-result", storedResult, direct.getStore(), shaped.getStore(), transforms);
|
|
bound.bindStore(shaped, (data) => data.value);
|
|
bound.runStores(data, () => show("rebound", shaped.getStore()));
|
|
const storeError = new Error("store body failed");
|
|
try { bound.runStores(data, () => { throw storeError; }); }
|
|
catch (error) { show("store-error", error === storeError, direct.getStore(), shaped.getStore()); }
|
|
show("unbind", bound.unbindStore(direct), bound.unbindStore(direct), bound.unbindStore(shaped), bound.hasSubscribers);
|
|
const storesOnly = tracingChannel("stores-only");
|
|
storesOnly.start.bindStore(direct);
|
|
const storeContext = { tag: "only store" };
|
|
show("stores-only", storesOnly.hasSubscribers, storesOnly.traceSync(() => direct.getStore() === storeContext, storeContext),
|
|
direct.getStore(), storesOnly.start.unbindStore(direct), storesOnly.hasSubscribers);
|
|
}
|
|
|
|
export async function reportAsync() {
|
|
const trace = tracingChannel("async-fixture");
|
|
const output = [];
|
|
const late = trace.tracePromise(() => new Promise((resolve) => setImmediate(resolve, "silent")), { tag: "silent" });
|
|
record(trace, output);
|
|
await late;
|
|
show("late-subscription", output.splice(0));
|
|
const context = { tag: "promise" };
|
|
const receiver = { prefix: "bound:" };
|
|
const result = trace.tracePromise(function (value) { return Promise.resolve(this.prefix + value); }, context, receiver, "value");
|
|
show("promise-before", output.splice(0), context.result);
|
|
show("promise-after", await result, context.result, output.splice(0));
|
|
const objectResult = { value: "same object" };
|
|
const identityContext = { tag: "identity" };
|
|
const identityResult = await trace.tracePromise(() => Promise.resolve(objectResult), identityContext);
|
|
show("promise-identity", identityResult === objectResult, identityContext.result === objectResult, output.splice(0));
|
|
for (const value of [42, { then(resolve) { resolve("thenable"); } }]) {
|
|
const ctx = { tag: "coerced" };
|
|
show("coerced", await trace.tracePromise(() => value, ctx), ctx.result, output.splice(0));
|
|
}
|
|
const rejection = new Error("promise rejection");
|
|
const rejectedContext = { tag: "rejected" };
|
|
try { await trace.tracePromise(() => Promise.reject(rejection), rejectedContext); }
|
|
catch (error) { show("promise-error", error === rejection, rejectedContext.error === rejection, output.splice(0)); }
|
|
const thrown = new Error("promise sync throw");
|
|
try { trace.tracePromise(() => { throw thrown; }, { tag: "sync throw" }); }
|
|
catch (error) { show("promise-sync-error", error === thrown, output.splice(0)); }
|
|
|
|
const store = new AsyncLocalStorage();
|
|
trace.start.bindStore(store, (data) => { data.span = { name: data.tag }; return data.span; });
|
|
trace.asyncStart.bindStore(store, (data) => data.span);
|
|
const callbackContext = { tag: "callback" };
|
|
await new Promise((resolve) => {
|
|
const callbackReceiver = { who: "callback receiver" };
|
|
const returned = trace.traceCallback(function (value, callback) {
|
|
show("callback-body", this === receiver, store.getStore() === callbackContext.span);
|
|
setImmediate(() => callback.call(callbackReceiver, null, value, "extra"));
|
|
return "call result";
|
|
}, -1, callbackContext, receiver, "callback value", function (error, value, extra) {
|
|
show("callback", error, value, extra, this === callbackReceiver,
|
|
store.getStore() === callbackContext.span, output.splice(0));
|
|
resolve();
|
|
return "ignored callback return";
|
|
});
|
|
show("callback-return", returned, output.splice(0), store.getStore());
|
|
});
|
|
show("callback-end", output.splice(0), store.getStore());
|
|
const callbackError = new Error("callback failure");
|
|
const failedContext = { tag: "callback error" };
|
|
trace.traceCallback((callback) => callback(callbackError, "ignored result"), 0, failedContext, null,
|
|
(error) => show("callback-error-body", error === callbackError, failedContext.error === error, store.getStore() === failedContext.span));
|
|
show("callback-error", failedContext.result, output.splice(0), store.getStore());
|
|
const callbackThrow = new Error("callback threw");
|
|
try { trace.traceCallback((callback) => callback(null, "success"), 0, { tag: "callback throw" }, null, () => { throw callbackThrow; }); }
|
|
catch (error) { show("callback-throw", error === callbackThrow, output.splice(0)); }
|
|
}
|
|
|
|
export function subscriberThrow() {
|
|
const ch = channel("throwing-subscriber");
|
|
ch.subscribe(() => { console.log("throwing subscriber"); throw new Error("subscriber failed"); });
|
|
ch.subscribe(() => console.log("later subscriber"));
|
|
ch.publish({});
|
|
console.log("publish returned");
|
|
setImmediate(() => console.log("unreachable immediate"));
|
|
}
|
|
|
|
export function transformThrow() {
|
|
const ch = channel("throwing-transform");
|
|
const store = new AsyncLocalStorage();
|
|
ch.bindStore(store, () => { console.log("transform"); throw new Error("transform failed"); });
|
|
ch.subscribe(() => show("subscriber-store", store.getStore()));
|
|
show("transform-result", ch.runStores({}, () => "body continued"), store.getStore());
|
|
setImmediate(() => console.log("unreachable immediate"));
|
|
}
|