Update
This commit is contained in:
@@ -6,6 +6,5 @@ export function getGlobal(): any {
|
||||
} else if (typeof window === "object") {
|
||||
return window;
|
||||
}
|
||||
|
||||
throw new Error("No global available");
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
VisualizationData,
|
||||
DataExtractionResult,
|
||||
} from "../../DataExtractionResult";
|
||||
import { DataExtractionResult, VisualizationData } from "../../DataExtractionResult";
|
||||
import { LoadDataExtractorsFn } from "./LoadDataExtractorsFn";
|
||||
|
||||
export interface DataExtractorApi {
|
||||
@@ -24,7 +21,8 @@ export interface DataExtractorApi {
|
||||
valueFn: () => unknown,
|
||||
evalFn: <T>(expression: string) => T,
|
||||
preferredDataExtractorId: string | undefined,
|
||||
variablesInScope: Record<string, unknown>
|
||||
variablesInScope: Record<string, unknown>,
|
||||
callFramesSnapshot: CallFramesSnapshot | null
|
||||
): JSONString<DataResult>;
|
||||
|
||||
/**
|
||||
@@ -42,12 +40,41 @@ export type DataResult =
|
||||
extractionResult: DataExtractionResult;
|
||||
}
|
||||
| { kind: "NoExtractors" }
|
||||
| { kind: "Error"; message: string };
|
||||
| { kind: "Error"; message: string }
|
||||
| {
|
||||
kind: "OutdatedCallFrameSnapshot";
|
||||
callFramesRequest: CallFramesRequest;
|
||||
};
|
||||
|
||||
export interface JSONString<T> extends String {
|
||||
__brand: { json: T };
|
||||
}
|
||||
|
||||
export interface CallFramesRequest {
|
||||
requestId: string;
|
||||
requestedCallFrames: CallFrameRequest[];
|
||||
}
|
||||
|
||||
export interface CallFrameRequest {
|
||||
methodName: string;
|
||||
pathRegExp: string | undefined;
|
||||
}
|
||||
|
||||
export interface CallFramesSnapshot {
|
||||
requestId: string;
|
||||
frames: (CallFrameInfo | SkippedCallFrames)[];
|
||||
}
|
||||
|
||||
export interface SkippedCallFrames {
|
||||
skippedFrames: number;
|
||||
}
|
||||
|
||||
export interface CallFrameInfo {
|
||||
methodName: string;
|
||||
source: { name: string; path: string };
|
||||
vars: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface DataExtractor {
|
||||
/**
|
||||
* Must be unique among all data extractors.
|
||||
@@ -58,11 +85,7 @@ export interface DataExtractor {
|
||||
* Filters the data to be extracted.
|
||||
*/
|
||||
dataCtor?: string;
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
extractionCollector: ExtractionCollector,
|
||||
context: DataExtractorContext
|
||||
): void;
|
||||
getExtractions(data: unknown, extractionCollector: ExtractionCollector, context: DataExtractorContext): void;
|
||||
}
|
||||
|
||||
export interface ExtractionCollector {
|
||||
@@ -78,11 +101,15 @@ export interface DataExtractorContext {
|
||||
*/
|
||||
evalFn: <TEval>(expression: string) => TEval;
|
||||
|
||||
expression: string | undefined;
|
||||
readonly expression: string | undefined;
|
||||
|
||||
variablesInScope: Record<string, () => unknown>;
|
||||
readonly variablesInScope: Record<string, () => unknown>;
|
||||
|
||||
extract(value: unknown): VisualizationData | undefined;
|
||||
|
||||
readonly callFrameInfos: readonly (CallFrameInfo | SkippedCallFrames)[];
|
||||
|
||||
addCallFrameRequest(request: CallFrameRequest): void;
|
||||
}
|
||||
|
||||
export interface DataExtraction {
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import {
|
||||
DataExtractorApi,
|
||||
DataResult,
|
||||
JSONString,
|
||||
DataExtractor,
|
||||
DataExtraction,
|
||||
ExtractionCollector,
|
||||
DataExtractorContext,
|
||||
} from "./DataExtractorApi";
|
||||
import {
|
||||
DataExtractorInfo,
|
||||
VisualizationData,
|
||||
} from "../../DataExtractionResult";
|
||||
import { registerDefaultExtractors } from "./default-extractors";
|
||||
import { LoadDataExtractorsFn } from "./LoadDataExtractorsFn";
|
||||
import * as helpers from "../helpers";
|
||||
import {
|
||||
CallFrameInfo,
|
||||
CallFrameRequest,
|
||||
CallFramesSnapshot,
|
||||
DataExtraction,
|
||||
DataExtractor,
|
||||
DataExtractorApi,
|
||||
DataExtractorContext,
|
||||
DataResult,
|
||||
JSONString,
|
||||
SkippedCallFrames,
|
||||
} from "./DataExtractorApi";
|
||||
import { LoadDataExtractorsFn } from "./LoadDataExtractorsFn";
|
||||
import { registerDefaultExtractors } from "./default-extractors";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@@ -42,51 +45,18 @@ export class DataExtractorApiImpl implements DataExtractorApi {
|
||||
valueFn: () => unknown,
|
||||
evalFn: <T>(expression: string) => T,
|
||||
preferredDataExtractorId: string | undefined,
|
||||
variablesInScope: Record<string, () => unknown>
|
||||
variablesInScope: Record<string, () => unknown>,
|
||||
callFramesSnapshot: CallFramesSnapshot | null
|
||||
): JSONString<DataResult> {
|
||||
class ContextImpl implements DataExtractorContext {
|
||||
constructor(
|
||||
public readonly variablesInScope: Record<string, () => unknown>,
|
||||
public readonly expression: string | undefined,
|
||||
public readonly evalFn: <T>(expression: string) => T,
|
||||
private readonly _api: DataExtractorApiImpl,
|
||||
private readonly _parent: ContextImpl | undefined
|
||||
) {}
|
||||
|
||||
get _level(): number {
|
||||
return this._parent ? this._parent._level + 1 : 0;
|
||||
}
|
||||
|
||||
extract(value: any): VisualizationData | undefined {
|
||||
if (this._level > 10) {
|
||||
throw new Error(
|
||||
"extract() called too many times recursively"
|
||||
);
|
||||
}
|
||||
|
||||
const extractions = this._api.getExtractions(
|
||||
value,
|
||||
new ContextImpl(
|
||||
this.variablesInScope,
|
||||
undefined,
|
||||
this.evalFn,
|
||||
this._api,
|
||||
this
|
||||
)
|
||||
);
|
||||
if (extractions.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return extractions[0].extractData();
|
||||
}
|
||||
}
|
||||
|
||||
const callFrameRequests: CallFrameRequest[] = [];
|
||||
const rootContext = new ContextImpl(
|
||||
variablesInScope,
|
||||
removeEnd(removeStart(valueFn.toString(), "() => ("), ")").trim(),
|
||||
evalFn,
|
||||
this,
|
||||
undefined
|
||||
undefined,
|
||||
callFramesSnapshot?.frames ?? [],
|
||||
callFrameRequests
|
||||
);
|
||||
|
||||
DataExtractorApiImpl.lastContext = rootContext;
|
||||
@@ -94,6 +64,20 @@ export class DataExtractorApiImpl implements DataExtractorApi {
|
||||
const extractions = this.getExtractions(value, rootContext);
|
||||
DataExtractorApiImpl.lastContext = undefined;
|
||||
|
||||
const requestId =
|
||||
callFrameRequests.length === 0
|
||||
? ""
|
||||
: "" + cyrb53(JSON.stringify(callFrameRequests));
|
||||
if ((callFramesSnapshot?.requestId ?? "") !== requestId) {
|
||||
return this.toJson({
|
||||
kind: "OutdatedCallFrameSnapshot",
|
||||
callFramesRequest: {
|
||||
requestId,
|
||||
requestedCallFrames: callFrameRequests,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let usedExtraction = extractions[0];
|
||||
if (!usedExtraction) {
|
||||
return this.toJson({ kind: "NoExtractors" } as DataResult);
|
||||
@@ -108,14 +92,6 @@ export class DataExtractorApiImpl implements DataExtractorApi {
|
||||
}
|
||||
}
|
||||
|
||||
function mapExtractor(e: DataExtraction): DataExtractorInfo {
|
||||
return {
|
||||
id: e.id! as any,
|
||||
name: e.name!,
|
||||
priority: e.priority,
|
||||
};
|
||||
}
|
||||
|
||||
const data = usedExtraction.extractData();
|
||||
return this.toJson({
|
||||
kind: "Data",
|
||||
@@ -188,6 +164,60 @@ export class DataExtractorApiImpl implements DataExtractorApi {
|
||||
}
|
||||
}
|
||||
|
||||
function mapExtractor(e: DataExtraction): DataExtractorInfo {
|
||||
return {
|
||||
id: e.id! as any,
|
||||
name: e.name!,
|
||||
priority: e.priority,
|
||||
};
|
||||
}
|
||||
|
||||
class ContextImpl implements DataExtractorContext {
|
||||
constructor(
|
||||
public readonly variablesInScope: Record<string, () => unknown>,
|
||||
public readonly expression: string | undefined,
|
||||
public readonly evalFn: <T>(expression: string) => T,
|
||||
private readonly _api: DataExtractorApiImpl,
|
||||
private readonly _parent: ContextImpl | undefined,
|
||||
public readonly callFrameInfos: readonly (
|
||||
| CallFrameInfo
|
||||
| SkippedCallFrames
|
||||
)[],
|
||||
private readonly _callFrameRequests: CallFrameRequest[]
|
||||
) {}
|
||||
|
||||
addCallFrameRequest(request: CallFrameRequest): void {
|
||||
this._callFrameRequests.push(request);
|
||||
}
|
||||
|
||||
get _level(): number {
|
||||
return this._parent ? this._parent._level + 1 : 0;
|
||||
}
|
||||
|
||||
extract(value: any): VisualizationData | undefined {
|
||||
if (this._level > 10) {
|
||||
throw new Error("extract() called too many times recursively");
|
||||
}
|
||||
|
||||
const extractions = this._api.getExtractions(
|
||||
value,
|
||||
new ContextImpl(
|
||||
this.variablesInScope,
|
||||
undefined,
|
||||
this.evalFn,
|
||||
this._api,
|
||||
this,
|
||||
this.callFrameInfos,
|
||||
this._callFrameRequests
|
||||
)
|
||||
);
|
||||
if (extractions.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return extractions[0].extractData();
|
||||
}
|
||||
}
|
||||
|
||||
function removeStart(str: string, start: string): string {
|
||||
if (str.startsWith(start)) {
|
||||
return str.substr(start.length);
|
||||
@@ -201,3 +231,20 @@ function removeEnd(str: string, end: string): string {
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
// From https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript
|
||||
function cyrb53(str: string, seed = 0) {
|
||||
let h1 = 0xdeadbeef ^ seed,
|
||||
h2 = 0x41c6ce57 ^ seed;
|
||||
for (let i = 0, ch; i < str.length; i++) {
|
||||
ch = str.charCodeAt(i);
|
||||
h1 = Math.imul(h1 ^ ch, 2654435761);
|
||||
h2 = Math.imul(h2 ^ ch, 1597334677);
|
||||
}
|
||||
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
|
||||
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
||||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
|
||||
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
||||
|
||||
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as fs from "fs";
|
||||
import { getGlobal } from "../../getGlobal";
|
||||
import * as globalHelpers from "../global-helpers";
|
||||
import * as helpers from "../helpers";
|
||||
import { DataExtractorApi } from "./DataExtractorApi";
|
||||
import { DataExtractorApiImpl } from "./DataExtractorApiImpl";
|
||||
import * as helpers from "../helpers";
|
||||
import * as globalHelpers from "../global-helpers";
|
||||
import { getGlobal } from "../../getGlobal";
|
||||
|
||||
/**
|
||||
* Returns standalone JS code representing an expression that initializes the data extraction API.
|
||||
@@ -50,8 +50,18 @@ export function getDataExtractorApi(): DataExtractorApi {
|
||||
* @internal
|
||||
*/
|
||||
function selfContainedGetInitializedDataExtractorApi(): DataExtractorApi {
|
||||
const globalObj =
|
||||
typeof window === "object" ? (window as any) : (global as any);
|
||||
function getGlobal(): any {
|
||||
if (typeof globalThis === "object") {
|
||||
return globalThis;
|
||||
} else if (typeof global === "object") {
|
||||
return global;
|
||||
} else if (typeof window === "object") {
|
||||
return window;
|
||||
}
|
||||
throw new Error("No global available");
|
||||
}
|
||||
|
||||
const globalObj = getGlobal();
|
||||
const key: typeof apiKey = "@hediet/data-extractor-api/v3";
|
||||
let api: DataExtractorApi | undefined = globalObj[key];
|
||||
if (!api) {
|
||||
|
||||
@@ -30,7 +30,12 @@ export function createGraphFromPointers<T>(
|
||||
return createGraph<T | Pointer>(
|
||||
items,
|
||||
(item) => {
|
||||
if ("marker" in item && item["marker"] === marker) {
|
||||
if (
|
||||
typeof item === "object" &&
|
||||
item &&
|
||||
"marker" in item &&
|
||||
item["marker"] === marker
|
||||
) {
|
||||
return {
|
||||
id: "label____" + item.name,
|
||||
color: "orange",
|
||||
|
||||
Reference in New Issue
Block a user