whet 0.4.0 → 0.5.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.
- package/bin/haxe/Exception.d.ts +1 -0
- package/bin/haxe/Exception.js +3 -0
- package/bin/haxe/ValueException.d.ts +9 -0
- package/bin/haxe/ValueException.js +11 -0
- package/bin/whet/Stone.d.ts +46 -0
- package/bin/whet/Stone.js +70 -0
- package/bin/whet/Whet.js +1 -1
- package/bin/whet/cache/MemoContext.d.ts +1 -0
- package/bin/whet/cache/MemoContext.js +2 -0
- package/bin/whet/profiler/Profiler.d.ts +6 -1
- package/bin/whet/profiler/Profiler.js +28 -16
- package/bin/whet/route/Router.d.ts +13 -0
- package/bin/whet/route/Router.js +50 -0
- package/package.json +1 -1
package/bin/haxe/Exception.d.ts
CHANGED
package/bin/haxe/Exception.js
CHANGED
|
@@ -52,6 +52,9 @@ class Exception extends Register.inherits(() => Error, true) {
|
|
|
52
52
|
this.__previousException = previous;
|
|
53
53
|
this.__nativeException = ($native != null) ? $native : this;
|
|
54
54
|
}
|
|
55
|
+
unwrap() {
|
|
56
|
+
return this.__nativeException;
|
|
57
|
+
}
|
|
55
58
|
|
|
56
59
|
/**
|
|
57
60
|
Returns exception message.
|
|
@@ -21,4 +21,13 @@ export declare class ValueException extends Exception {
|
|
|
21
21
|
Thrown value.
|
|
22
22
|
*/
|
|
23
23
|
value: any
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
Extract an originally thrown value.
|
|
27
|
+
|
|
28
|
+
This method must return the same value on subsequent calls.
|
|
29
|
+
Used internally for catching non-native exceptions.
|
|
30
|
+
Do _not_ override unless you know what you are doing.
|
|
31
|
+
*/
|
|
32
|
+
protected unwrap(): any
|
|
24
33
|
}
|
|
@@ -23,6 +23,17 @@ class ValueException extends Register.inherits(() => Exception, true) {
|
|
|
23
23
|
super[Register.new](String(value), previous, $native);
|
|
24
24
|
this.value = value;
|
|
25
25
|
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
Extract an originally thrown value.
|
|
29
|
+
|
|
30
|
+
This method must return the same value on subsequent calls.
|
|
31
|
+
Used internally for catching non-native exceptions.
|
|
32
|
+
Do _not_ override unless you know what you are doing.
|
|
33
|
+
*/
|
|
34
|
+
unwrap() {
|
|
35
|
+
return this.value;
|
|
36
|
+
}
|
|
26
37
|
static get __name__() {
|
|
27
38
|
return "haxe.ValueException"
|
|
28
39
|
}
|
package/bin/whet/Stone.d.ts
CHANGED
|
@@ -121,6 +121,52 @@ export declare class Stone<T extends StoneConfig> {
|
|
|
121
121
|
*/
|
|
122
122
|
protected generatePartial(sourceId: string, hash: SourceHash): Promise<null | SourceData[]>
|
|
123
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Optional override: compute expensive shared state once and reuse it across all
|
|
126
|
+
* `list()` / `generatePartial()` calls within a generation batch. Without this, work like
|
|
127
|
+
* enumerating inputs, resolving pipelines, or building lookup maps gets repeated once per output.
|
|
128
|
+
*
|
|
129
|
+
* Override to do the upfront work; call `getContext()` (not this) from `list()`/`generatePartial()`
|
|
130
|
+
* to read it. The result is cached (see `getContext` for the caching rules). The returned object
|
|
131
|
+
* is held by reference and never serialized — Maps, class instances, and closures are fine.
|
|
132
|
+
*
|
|
133
|
+
* **Constraint**: `generateContext` must not call back into this *same* stone's `getSource()` /
|
|
134
|
+
* `getPartialSource()` — it runs inside this stone's `acquire()` lock and would deadlock. Reading
|
|
135
|
+
* from *other* stones (dependencies) is fine.
|
|
136
|
+
*/
|
|
137
|
+
protected generateContext(hash: SourceHash): Promise<any>
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
Instance-level context cache, keyed by a stable (non-null) hash. See getContext.
|
|
141
|
+
*/
|
|
142
|
+
protected _contextPromise: null | {
|
|
143
|
+
hash: SourceHash,
|
|
144
|
+
promise: Promise<any>
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* **Do not override** (override `generateContext` instead). Returns the shared context,
|
|
149
|
+
* computing it via `generateContext` at most once per key. Callable from `list()`,
|
|
150
|
+
* `generatePartial()`, or `generateHash()`.
|
|
151
|
+
*
|
|
152
|
+
* Caching:
|
|
153
|
+
* - With a stable (non-null) hash, the resolved Promise is cached on the stone instance keyed by
|
|
154
|
+
* that hash, so it is reused across separate `getSource()`/`getPartialSource()` calls — even
|
|
155
|
+
* across builds — as long as the hash matches. A changed hash recomputes.
|
|
156
|
+
* - With a null hash (a stone without `generateHash()`), there is no stable key, so the context is
|
|
157
|
+
* scoped to the current request via `MemoContext` instead of the instance. This still shares it
|
|
158
|
+
* across the `Promise.all` batch in the default `generate()`, but avoids holding stale state
|
|
159
|
+
* across builds.
|
|
160
|
+
*
|
|
161
|
+
* In both cases the *Promise* (not the resolved value) is cached, so concurrent callers from the
|
|
162
|
+
* same batch share one in-flight computation rather than racing to start their own.
|
|
163
|
+
*
|
|
164
|
+
* @param hash Pass the hash when you already have it (from `generatePartial`); omit it elsewhere
|
|
165
|
+
* (e.g. `list()`) and it is derived via `finalMaybeHash()` without forcing generation.
|
|
166
|
+
*/
|
|
167
|
+
protected getContext(hash?: null | SourceHash): Promise<any>
|
|
168
|
+
protected _contextForHash(hash: null | SourceHash): Promise<any>
|
|
169
|
+
|
|
124
170
|
/**
|
|
125
171
|
* Get source for a single output by sourceId.
|
|
126
172
|
* If the stone implements generatePartial(), generates just the requested output.
|
package/bin/whet/Stone.js
CHANGED
|
@@ -16,6 +16,7 @@ const $global = Register.$global
|
|
|
16
16
|
export const Stone = Register.global("$hxClasses")["whet.Stone"] =
|
|
17
17
|
class Stone extends Register.inherits() {
|
|
18
18
|
[Register.new](config) {
|
|
19
|
+
this._contextPromise = null;
|
|
19
20
|
this.locked = false;
|
|
20
21
|
this.lockQueue = [];
|
|
21
22
|
this.ignoreFileHash = false;
|
|
@@ -396,6 +397,74 @@ class Stone extends Register.inherits() {
|
|
|
396
397
|
return Promise.resolve(null);
|
|
397
398
|
}
|
|
398
399
|
|
|
400
|
+
/**
|
|
401
|
+
* Optional override: compute expensive shared state once and reuse it across all
|
|
402
|
+
* `list()` / `generatePartial()` calls within a generation batch. Without this, work like
|
|
403
|
+
* enumerating inputs, resolving pipelines, or building lookup maps gets repeated once per output.
|
|
404
|
+
*
|
|
405
|
+
* Override to do the upfront work; call `getContext()` (not this) from `list()`/`generatePartial()`
|
|
406
|
+
* to read it. The result is cached (see `getContext` for the caching rules). The returned object
|
|
407
|
+
* is held by reference and never serialized — Maps, class instances, and closures are fine.
|
|
408
|
+
*
|
|
409
|
+
* **Constraint**: `generateContext` must not call back into this *same* stone's `getSource()` /
|
|
410
|
+
* `getPartialSource()` — it runs inside this stone's `acquire()` lock and would deadlock. Reading
|
|
411
|
+
* from *other* stones (dependencies) is fine.
|
|
412
|
+
*/
|
|
413
|
+
generateContext(hash) {
|
|
414
|
+
return Promise.resolve(null);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* **Do not override** (override `generateContext` instead). Returns the shared context,
|
|
419
|
+
* computing it via `generateContext` at most once per key. Callable from `list()`,
|
|
420
|
+
* `generatePartial()`, or `generateHash()`.
|
|
421
|
+
*
|
|
422
|
+
* Caching:
|
|
423
|
+
* - With a stable (non-null) hash, the resolved Promise is cached on the stone instance keyed by
|
|
424
|
+
* that hash, so it is reused across separate `getSource()`/`getPartialSource()` calls — even
|
|
425
|
+
* across builds — as long as the hash matches. A changed hash recomputes.
|
|
426
|
+
* - With a null hash (a stone without `generateHash()`), there is no stable key, so the context is
|
|
427
|
+
* scoped to the current request via `MemoContext` instead of the instance. This still shares it
|
|
428
|
+
* across the `Promise.all` batch in the default `generate()`, but avoids holding stale state
|
|
429
|
+
* across builds.
|
|
430
|
+
*
|
|
431
|
+
* In both cases the *Promise* (not the resolved value) is cached, so concurrent callers from the
|
|
432
|
+
* same batch share one in-flight computation rather than racing to start their own.
|
|
433
|
+
*
|
|
434
|
+
* @param hash Pass the hash when you already have it (from `generatePartial`); omit it elsewhere
|
|
435
|
+
* (e.g. `list()`) and it is derived via `finalMaybeHash()` without forcing generation.
|
|
436
|
+
*/
|
|
437
|
+
getContext(hash) {
|
|
438
|
+
if (hash != null) {
|
|
439
|
+
return this._contextForHash(hash);
|
|
440
|
+
};
|
|
441
|
+
let _gthis = this;
|
|
442
|
+
return this.finalMaybeHash().then(function (h) {
|
|
443
|
+
return _gthis._contextForHash(h);
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
_contextForHash(hash) {
|
|
447
|
+
if (hash == null) {
|
|
448
|
+
let ctx = MemoContext.als.getStore();
|
|
449
|
+
if (ctx == null) {
|
|
450
|
+
return this.generateContext(null);
|
|
451
|
+
};
|
|
452
|
+
let cached = ctx.contexts.get(this);
|
|
453
|
+
if (cached != null) {
|
|
454
|
+
return cached;
|
|
455
|
+
};
|
|
456
|
+
let p = this.generateContext(null);
|
|
457
|
+
ctx.contexts.set(this, p);
|
|
458
|
+
return p;
|
|
459
|
+
};
|
|
460
|
+
if (this._contextPromise != null && this._contextPromise.hash != null && SourceHash.equals(this._contextPromise.hash, hash)) {
|
|
461
|
+
return this._contextPromise.promise;
|
|
462
|
+
};
|
|
463
|
+
let p = this.generateContext(hash);
|
|
464
|
+
this._contextPromise = {"hash": hash, "promise": p};
|
|
465
|
+
return p;
|
|
466
|
+
}
|
|
467
|
+
|
|
399
468
|
/**
|
|
400
469
|
* Get source for a single output by sourceId.
|
|
401
470
|
* If the stone implements generatePartial(), generates just the requested output.
|
|
@@ -646,4 +715,5 @@ Stone.prototype.cacheStrategy = null;
|
|
|
646
715
|
Stone.prototype.project = null;
|
|
647
716
|
Stone.prototype.lockQueue = null;
|
|
648
717
|
Stone.prototype.locked = null;
|
|
718
|
+
Stone.prototype._contextPromise = null;
|
|
649
719
|
|
package/bin/whet/Whet.js
CHANGED
|
@@ -26,7 +26,7 @@ class Whet_Fields_ {
|
|
|
26
26
|
if (entryUrl != thisUrl) {
|
|
27
27
|
return;
|
|
28
28
|
};
|
|
29
|
-
Whet_Fields_.program.enablePositionalOptions().passThroughOptions().description("Project tooling.").usage("[options] [command] [+ [command]...]").version("0.
|
|
29
|
+
Whet_Fields_.program.enablePositionalOptions().passThroughOptions().description("Project tooling.").usage("[options] [command] [+ [command]...]").version("0.5.0", "-v, --version").allowUnknownOption(true).allowExcessArguments(true).showSuggestionAfterError(true).option("-p, --project <file>", "project to run", "Project.mjs").option("-l, --log-level <level>", "log level, a string/number", "info").option("--no-pretty", "disable pretty logging").option("--profile <format>", "enable profiling, export to whet-profile.json on exit (format: json or trace, default: json)").exitOverride();
|
|
30
30
|
try {
|
|
31
31
|
Whet_Fields_.program.parse();
|
|
32
32
|
}catch (_g) {
|
|
@@ -8,6 +8,7 @@ export declare class MemoContext {
|
|
|
8
8
|
protected sources: Map<AnyStone, Promise<Source>>
|
|
9
9
|
protected hashes: Map<AnyStone, Promise<SourceHash>>
|
|
10
10
|
protected partials: Map<AnyStone, Map<string, Promise<null | Source>>>
|
|
11
|
+
protected contexts: Map<AnyStone, Promise<any>>
|
|
11
12
|
protected static als: AsyncLocalStorage<MemoContext>
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -6,6 +6,7 @@ const $global = Register.$global
|
|
|
6
6
|
export const MemoContext = Register.global("$hxClasses")["whet.cache.MemoContext"] =
|
|
7
7
|
class MemoContext extends Register.inherits() {
|
|
8
8
|
[Register.new]() {
|
|
9
|
+
this.contexts = new Map();
|
|
9
10
|
this.partials = new Map();
|
|
10
11
|
this.hashes = new Map();
|
|
11
12
|
this.sources = new Map();
|
|
@@ -30,6 +31,7 @@ class MemoContext extends Register.inherits() {
|
|
|
30
31
|
MemoContext.prototype.sources = null;
|
|
31
32
|
MemoContext.prototype.hashes = null;
|
|
32
33
|
MemoContext.prototype.partials = null;
|
|
34
|
+
MemoContext.prototype.contexts = null;
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
MemoContext.als = new AsyncLocalStorage()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {SpanStats} from "./SpanStats"
|
|
2
2
|
import {SpanRecorder} from "./SpanRecorder"
|
|
3
|
-
import {SpanEvent, AnySpan, SpanEventType} from "./Span"
|
|
3
|
+
import {SpanEvent, AnySpan, SpanStatus, SpanEventType} from "./Span"
|
|
4
4
|
import {AnyStone} from "../Stone"
|
|
5
5
|
import {AsyncLocalStorage} from "node:async_hooks"
|
|
6
6
|
|
|
@@ -19,6 +19,11 @@ export declare class Profiler {
|
|
|
19
19
|
*/
|
|
20
20
|
withSpan<T, R>(stone: AnyStone, op: string, fn: (() => Promise<R>), meta?: null | T): Promise<R>
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
Finalize a span's timing/status, record it, and emit the End event.
|
|
24
|
+
*/
|
|
25
|
+
protected finishSpan(span: AnySpan, status: SpanStatus): void
|
|
26
|
+
|
|
22
27
|
/**
|
|
23
28
|
Secondary API: manual start for spans where no function can be wrapped (e.g. LockWait).
|
|
24
29
|
*/
|
|
@@ -3,6 +3,7 @@ import {SpanRecorder} from "./SpanRecorder.js"
|
|
|
3
3
|
import {Span, SpanEventType, SpanStatus} from "./Span.js"
|
|
4
4
|
import {AsyncLocalStorage} from "node:async_hooks"
|
|
5
5
|
import {IntMap} from "../../haxe/ds/IntMap.js"
|
|
6
|
+
import {Exception} from "../../haxe/Exception.js"
|
|
6
7
|
import {EsMap} from "../../genes/util/EsMap.js"
|
|
7
8
|
import {Register} from "../../genes/Register.js"
|
|
8
9
|
import {Std} from "../../Std.js"
|
|
@@ -33,25 +34,36 @@ class Profiler extends Register.inherits() {
|
|
|
33
34
|
this.emit(SpanEventType.Start, span);
|
|
34
35
|
let _gthis = this;
|
|
35
36
|
return this.context.run(span, function () {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
_gthis.emit(SpanEventType.End, span);
|
|
50
|
-
return Promise.reject(err);
|
|
51
|
-
});
|
|
37
|
+
try {
|
|
38
|
+
return fn().then(function (result) {
|
|
39
|
+
_gthis.finishSpan(span, SpanStatus.Ok);
|
|
40
|
+
return result;
|
|
41
|
+
})["catch"](function (err) {
|
|
42
|
+
_gthis.finishSpan(span, SpanStatus.Error(Std.string(err)));
|
|
43
|
+
return Promise.reject(err);
|
|
44
|
+
});
|
|
45
|
+
}catch (_g) {
|
|
46
|
+
let _g1 = Exception.caught(_g).unwrap();
|
|
47
|
+
_gthis.finishSpan(span, SpanStatus.Error(Std.string(_g1)));
|
|
48
|
+
return Promise.reject(_g1);
|
|
49
|
+
};
|
|
52
50
|
});
|
|
53
51
|
}
|
|
54
52
|
|
|
53
|
+
/**
|
|
54
|
+
Finalize a span's timing/status, record it, and emit the End event.
|
|
55
|
+
*/
|
|
56
|
+
finishSpan(span, status) {
|
|
57
|
+
span.endTime = performance.now();
|
|
58
|
+
span.duration = span.endTime - span.startTime;
|
|
59
|
+
span.status = status;
|
|
60
|
+
this.recorder.record(span);
|
|
61
|
+
if (status == SpanStatus.Ok) {
|
|
62
|
+
this.stats.update(span.stone, span.operation, span.duration);
|
|
63
|
+
};
|
|
64
|
+
this.emit(SpanEventType.End, span);
|
|
65
|
+
}
|
|
66
|
+
|
|
55
67
|
/**
|
|
56
68
|
Secondary API: manual start for spans where no function can be wrapped (e.g. LockWait).
|
|
57
69
|
*/
|
|
@@ -34,6 +34,19 @@ export declare class Router {
|
|
|
34
34
|
*/
|
|
35
35
|
getHash(pattern?: null | MinimatchType): Promise<SourceHash>
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Hash for the no-query-filter case: a per-route token binding the source's hash to the
|
|
39
|
+
* static route structure (mount path, filter, extractDirs), then merged order-independently.
|
|
40
|
+
* Child routers recurse through getHash() with no pattern, so nested structure folds in at each
|
|
41
|
+
* level. No id enumeration, so stones without list() are not forced to generate.
|
|
42
|
+
*
|
|
43
|
+
* Binding structure to source per route is what keeps this false-hit-safe: the set of served
|
|
44
|
+
* ids is a function of each route's (mount/filter/extractDirs) plus its source id-set, and the
|
|
45
|
+
* source's hash moves with its id-set. Sorting the per-route tokens makes route order
|
|
46
|
+
* irrelevant (matching the long-standing contract) without losing that binding.
|
|
47
|
+
*/
|
|
48
|
+
protected getUnfilteredHash(): Promise<SourceHash>
|
|
49
|
+
|
|
37
50
|
/**
|
|
38
51
|
* Save files filtered by `pattern` into provided `saveInto` folder.
|
|
39
52
|
*/
|
package/bin/whet/route/Router.js
CHANGED
|
@@ -219,6 +219,11 @@ class Router extends Register.inherits() {
|
|
|
219
219
|
*/
|
|
220
220
|
getHash(pattern) {
|
|
221
221
|
let _gthis = this;
|
|
222
|
+
if (pattern == null) {
|
|
223
|
+
return MemoContext.ensure(function () {
|
|
224
|
+
return _gthis.getUnfilteredHash();
|
|
225
|
+
});
|
|
226
|
+
};
|
|
222
227
|
return MemoContext.ensure(function () {
|
|
223
228
|
return _gthis.get(pattern).then(function (items) {
|
|
224
229
|
let uniqueStones = [];
|
|
@@ -264,6 +269,51 @@ class Router extends Register.inherits() {
|
|
|
264
269
|
});
|
|
265
270
|
}
|
|
266
271
|
|
|
272
|
+
/**
|
|
273
|
+
* Hash for the no-query-filter case: a per-route token binding the source's hash to the
|
|
274
|
+
* static route structure (mount path, filter, extractDirs), then merged order-independently.
|
|
275
|
+
* Child routers recurse through getHash() with no pattern, so nested structure folds in at each
|
|
276
|
+
* level. No id enumeration, so stones without list() are not forced to generate.
|
|
277
|
+
*
|
|
278
|
+
* Binding structure to source per route is what keeps this false-hit-safe: the set of served
|
|
279
|
+
* ids is a function of each route's (mount/filter/extractDirs) plus its source id-set, and the
|
|
280
|
+
* source's hash moves with its id-set. Sorting the per-route tokens makes route order
|
|
281
|
+
* irrelevant (matching the long-standing contract) without losing that binding.
|
|
282
|
+
*/
|
|
283
|
+
getUnfilteredHash() {
|
|
284
|
+
let tokenProms = [];
|
|
285
|
+
let _g = 0;
|
|
286
|
+
let _g1 = this.routes;
|
|
287
|
+
while (_g < _g1.length) {
|
|
288
|
+
let route = _g1[_g];
|
|
289
|
+
++_g;
|
|
290
|
+
let structure = route.routeUnder + "|" + ((route.filter != null) ? route.filter.pattern : "") + "|" + ((route.extractDirs != null) ? route.extractDirs.pattern : "");
|
|
291
|
+
let childHash;
|
|
292
|
+
if (((route.source) instanceof Stone)) {
|
|
293
|
+
childHash = route.source.getHash();
|
|
294
|
+
} else if (((route.source) instanceof Router)) {
|
|
295
|
+
childHash = route.source.getHash();
|
|
296
|
+
} else {
|
|
297
|
+
throw new Error("Router source must be a Stone or a Router.");
|
|
298
|
+
};
|
|
299
|
+
tokenProms.push(childHash.then(function (h) {
|
|
300
|
+
return SourceHash.fromString(structure).add(h);
|
|
301
|
+
}));
|
|
302
|
+
};
|
|
303
|
+
return Promise.all(tokenProms).then(function (tokens) {
|
|
304
|
+
tokens.sort(function (a, b) {
|
|
305
|
+
if (a.toString() < b.toString()) {
|
|
306
|
+
return -1;
|
|
307
|
+
} else if (a.toString() > b.toString()) {
|
|
308
|
+
return 1;
|
|
309
|
+
} else {
|
|
310
|
+
return 0;
|
|
311
|
+
};
|
|
312
|
+
});
|
|
313
|
+
return SourceHash.merge(...tokens);
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
|
|
267
317
|
/**
|
|
268
318
|
* Save files filtered by `pattern` into provided `saveInto` folder.
|
|
269
319
|
*/
|
package/package.json
CHANGED