whet 0.2.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/genes/util/EsMap.d.ts +1 -0
- package/bin/genes/util/EsMap.js +3 -0
- package/bin/haxe/Constraints.d.ts +1 -0
- 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/Log.js +3 -0
- package/bin/whet/Project.d.ts +5 -0
- package/bin/whet/Project.js +12 -0
- package/bin/whet/Stone.d.ts +48 -0
- package/bin/whet/Stone.js +162 -1
- package/bin/whet/Whet.d.ts +1 -1
- package/bin/whet/Whet.js +49 -1
- package/bin/whet/cache/BaseCache.d.ts +5 -0
- package/bin/whet/cache/BaseCache.js +7 -0
- package/bin/whet/cache/CacheManager.d.ts +5 -0
- package/bin/whet/cache/CacheManager.js +8 -0
- package/bin/whet/cache/FileCache.d.ts +1 -0
- package/bin/whet/cache/FileCache.js +4 -0
- package/bin/whet/cache/MemoContext.d.ts +18 -0
- package/bin/whet/cache/MemoContext.js +37 -0
- package/bin/whet/magic/RoutePathType.js +5 -5
- package/bin/whet/profiler/Profiler.d.ts +6 -1
- package/bin/whet/profiler/Profiler.js +28 -16
- package/bin/whet/route/Router.d.ts +18 -0
- package/bin/whet/route/Router.js +97 -33
- package/bin/whet/stones/Files.d.ts +5 -0
- package/bin/whet/stones/Files.js +20 -0
- package/bin/whet/stones/StoneFactory.d.ts +55 -0
- package/bin/whet/stones/StoneFactory.js +116 -0
- package/bin/whet.d.ts +2 -0
- package/bin/whet.js +1 -0
- package/package.json +2 -2
package/bin/genes/util/EsMap.js
CHANGED
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/Log.js
CHANGED
package/bin/whet/Project.d.ts
CHANGED
|
@@ -24,6 +24,11 @@ export declare class Project {
|
|
|
24
24
|
config: any
|
|
25
25
|
protected options: Option[]
|
|
26
26
|
getStone(id: string): null | AnyStone
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
Remove a stone from the project. Does not clear cache — use cache.clearStone() separately if needed.
|
|
30
|
+
*/
|
|
31
|
+
removeStone(stone: AnyStone): boolean
|
|
27
32
|
describeStones(): StoneDescription[]
|
|
28
33
|
listStoneOutputs(id: string): Promise<null | string[]>
|
|
29
34
|
getStoneSource(id: string, sourceId?: null | string): Promise<null | Source>
|
package/bin/whet/Project.js
CHANGED
|
@@ -75,6 +75,18 @@ class Project extends Register.inherits() {
|
|
|
75
75
|
};
|
|
76
76
|
return null;
|
|
77
77
|
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
Remove a stone from the project. Does not clear cache — use cache.clearStone() separately if needed.
|
|
81
|
+
*/
|
|
82
|
+
removeStone(stone) {
|
|
83
|
+
let idx = this.stones.indexOf(stone);
|
|
84
|
+
if (idx == -1) {
|
|
85
|
+
return false;
|
|
86
|
+
};
|
|
87
|
+
this.stones.splice(idx, 1);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
78
90
|
describeStones() {
|
|
79
91
|
let _g = [];
|
|
80
92
|
let _g1 = 0;
|
package/bin/whet/Stone.d.ts
CHANGED
|
@@ -55,6 +55,7 @@ export declare class Stone<T extends StoneConfig> {
|
|
|
55
55
|
* Hashes of dependency stones (see `config.dependencies`) will be added to the hash.
|
|
56
56
|
*/
|
|
57
57
|
getHash(): Promise<SourceHash>
|
|
58
|
+
protected _computeHash(): Promise<SourceHash>
|
|
58
59
|
|
|
59
60
|
/**
|
|
60
61
|
* **Do not override.**
|
|
@@ -120,6 +121,52 @@ export declare class Stone<T extends StoneConfig> {
|
|
|
120
121
|
*/
|
|
121
122
|
protected generatePartial(sourceId: string, hash: SourceHash): Promise<null | SourceData[]>
|
|
122
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
|
+
|
|
123
170
|
/**
|
|
124
171
|
* Get source for a single output by sourceId.
|
|
125
172
|
* If the stone implements generatePartial(), generates just the requested output.
|
|
@@ -127,6 +174,7 @@ export declare class Stone<T extends StoneConfig> {
|
|
|
127
174
|
* Uses the same cache as getSource() — partial and full share the pool.
|
|
128
175
|
*/
|
|
129
176
|
getPartialSource(sourceId: string): Promise<null | Source>
|
|
177
|
+
protected _computePartialSource(sourceId: string): Promise<null | Source>
|
|
130
178
|
|
|
131
179
|
/**
|
|
132
180
|
* Called by cache infrastructure. Generates partial source directly,
|
package/bin/whet/Stone.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {StoneId_Fields_} from "./magic/StoneId.js"
|
|
2
2
|
import {MaybeArray_Fields_} from "./magic/MaybeArray.js"
|
|
3
|
+
import {MemoContext} from "./cache/MemoContext.js"
|
|
3
4
|
import {CacheStrategy, CacheDurability} from "./cache/Cache.js"
|
|
4
5
|
import {Utils} from "./Utils.js"
|
|
5
6
|
import {RootDir} from "./SourceId.js"
|
|
@@ -15,6 +16,7 @@ const $global = Register.$global
|
|
|
15
16
|
export const Stone = Register.global("$hxClasses")["whet.Stone"] =
|
|
16
17
|
class Stone extends Register.inherits() {
|
|
17
18
|
[Register.new](config) {
|
|
19
|
+
this._contextPromise = null;
|
|
18
20
|
this.locked = false;
|
|
19
21
|
this.lockQueue = [];
|
|
20
22
|
this.ignoreFileHash = false;
|
|
@@ -116,7 +118,24 @@ class Stone extends Register.inherits() {
|
|
|
116
118
|
*/
|
|
117
119
|
getSource() {
|
|
118
120
|
Log.log(20, ...["Getting source.", {"stone": this}]);
|
|
119
|
-
|
|
121
|
+
let ctx = MemoContext.als.getStore();
|
|
122
|
+
if (ctx != null) {
|
|
123
|
+
let cached = ctx.sources.get(this);
|
|
124
|
+
if (cached != null) {
|
|
125
|
+
Log.log(10, ...["Source memo hit.", {"stone": this}]);
|
|
126
|
+
return cached;
|
|
127
|
+
};
|
|
128
|
+
let p = this.project.cache.getSource(this);
|
|
129
|
+
ctx.sources.set(this, p);
|
|
130
|
+
return p;
|
|
131
|
+
};
|
|
132
|
+
let _gthis = this;
|
|
133
|
+
return MemoContext.ensure(function () {
|
|
134
|
+
let newCtx = MemoContext.als.getStore();
|
|
135
|
+
let p = _gthis.project.cache.getSource(_gthis);
|
|
136
|
+
newCtx.sources.set(_gthis, p);
|
|
137
|
+
return p;
|
|
138
|
+
});
|
|
120
139
|
}
|
|
121
140
|
|
|
122
141
|
/**
|
|
@@ -126,6 +145,44 @@ class Stone extends Register.inherits() {
|
|
|
126
145
|
*/
|
|
127
146
|
getHash() {
|
|
128
147
|
Log.log(20, ...["Generating hash.", {"stone": this}]);
|
|
148
|
+
let ctx = MemoContext.als.getStore();
|
|
149
|
+
if (ctx != null) {
|
|
150
|
+
let cached = ctx.hashes.get(this);
|
|
151
|
+
if (cached != null) {
|
|
152
|
+
Log.log(10, ...["Hash memo hit.", {"stone": this}]);
|
|
153
|
+
return cached;
|
|
154
|
+
};
|
|
155
|
+
let _gthis = this;
|
|
156
|
+
let p = this.finalMaybeHash().then(function (hash) {
|
|
157
|
+
if (hash != null) {
|
|
158
|
+
return hash;
|
|
159
|
+
} else {
|
|
160
|
+
return _gthis.getSource().then(function (s) {
|
|
161
|
+
return s.hash;
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
ctx.hashes.set(this, p);
|
|
166
|
+
return p;
|
|
167
|
+
};
|
|
168
|
+
let _gthis = this;
|
|
169
|
+
return MemoContext.ensure(function () {
|
|
170
|
+
let newCtx = MemoContext.als.getStore();
|
|
171
|
+
let _gthis1 = _gthis;
|
|
172
|
+
let p = _gthis.finalMaybeHash().then(function (hash) {
|
|
173
|
+
if (hash != null) {
|
|
174
|
+
return hash;
|
|
175
|
+
} else {
|
|
176
|
+
return _gthis1.getSource().then(function (s) {
|
|
177
|
+
return s.hash;
|
|
178
|
+
});
|
|
179
|
+
};
|
|
180
|
+
});
|
|
181
|
+
newCtx.hashes.set(_gthis, p);
|
|
182
|
+
return p;
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
_computeHash() {
|
|
129
186
|
let _gthis = this;
|
|
130
187
|
return this.finalMaybeHash().then(function (hash) {
|
|
131
188
|
if (hash != null) {
|
|
@@ -340,6 +397,74 @@ class Stone extends Register.inherits() {
|
|
|
340
397
|
return Promise.resolve(null);
|
|
341
398
|
}
|
|
342
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
|
+
|
|
343
468
|
/**
|
|
344
469
|
* Get source for a single output by sourceId.
|
|
345
470
|
* If the stone implements generatePartial(), generates just the requested output.
|
|
@@ -347,6 +472,41 @@ class Stone extends Register.inherits() {
|
|
|
347
472
|
* Uses the same cache as getSource() — partial and full share the pool.
|
|
348
473
|
*/
|
|
349
474
|
getPartialSource(sourceId) {
|
|
475
|
+
let ctx = MemoContext.als.getStore();
|
|
476
|
+
if (ctx != null) {
|
|
477
|
+
let partialMap = ctx.partials.get(this);
|
|
478
|
+
if (partialMap != null) {
|
|
479
|
+
let cached = partialMap.get(sourceId);
|
|
480
|
+
if (cached != null) {
|
|
481
|
+
Log.log(10, ...["Partial source memo hit.", {"stone": this, "sourceId": sourceId}]);
|
|
482
|
+
return cached;
|
|
483
|
+
};
|
|
484
|
+
};
|
|
485
|
+
let fullCached = ctx.sources.get(this);
|
|
486
|
+
if (fullCached != null) {
|
|
487
|
+
return fullCached.then(function (s) {
|
|
488
|
+
return s.filterTo(sourceId);
|
|
489
|
+
});
|
|
490
|
+
};
|
|
491
|
+
let p = this._computePartialSource(sourceId);
|
|
492
|
+
if (partialMap == null) {
|
|
493
|
+
partialMap = new Map();
|
|
494
|
+
ctx.partials.set(this, partialMap);
|
|
495
|
+
};
|
|
496
|
+
partialMap.set(sourceId, p);
|
|
497
|
+
return p;
|
|
498
|
+
};
|
|
499
|
+
let _gthis = this;
|
|
500
|
+
return MemoContext.ensure(function () {
|
|
501
|
+
let newCtx = MemoContext.als.getStore();
|
|
502
|
+
let p = _gthis._computePartialSource(sourceId);
|
|
503
|
+
let partialMap = new Map();
|
|
504
|
+
newCtx.partials.set(_gthis, partialMap);
|
|
505
|
+
partialMap.set(sourceId, p);
|
|
506
|
+
return p;
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
_computePartialSource(sourceId) {
|
|
350
510
|
let _gthis = this;
|
|
351
511
|
return this.finalMaybeHash().then(function (hash) {
|
|
352
512
|
if (hash == null) {
|
|
@@ -555,4 +715,5 @@ Stone.prototype.cacheStrategy = null;
|
|
|
555
715
|
Stone.prototype.project = null;
|
|
556
716
|
Stone.prototype.lockQueue = null;
|
|
557
717
|
Stone.prototype.locked = null;
|
|
718
|
+
Stone.prototype._contextPromise = null;
|
|
558
719
|
|
package/bin/whet/Whet.d.ts
CHANGED
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) {
|
|
@@ -87,6 +87,10 @@ class Whet_Fields_ {
|
|
|
87
87
|
while (_g2 < _g3.length) Whet_Fields_.program.addOption(_g3[_g2++]);
|
|
88
88
|
};
|
|
89
89
|
Whet_Fields_.program.allowUnknownOption(false);
|
|
90
|
+
let schemaCmd = new Command("schema");
|
|
91
|
+
schemaCmd.description("Export project schema as JSON.");
|
|
92
|
+
schemaCmd.action(Whet_Fields_.outputSchema);
|
|
93
|
+
Whet_Fields_.program.addCommand(schemaCmd);
|
|
90
94
|
let commands = Whet_Fields_.getCommands(Whet_Fields_.program.args);
|
|
91
95
|
let initProm;
|
|
92
96
|
if (commands.length > 0) {
|
|
@@ -150,6 +154,50 @@ class Whet_Fields_ {
|
|
|
150
154
|
nextCommand();
|
|
151
155
|
});
|
|
152
156
|
}
|
|
157
|
+
static outputSchema() {
|
|
158
|
+
Log.logLevel = 100;
|
|
159
|
+
let excludeCommands = ["help", "schema"];
|
|
160
|
+
let _g = [];
|
|
161
|
+
let _g1 = 0;
|
|
162
|
+
let _g2 = Project.projects;
|
|
163
|
+
while (_g1 < _g2.length) {
|
|
164
|
+
let p = _g2[_g1];
|
|
165
|
+
++_g1;
|
|
166
|
+
let p1 = p.name;
|
|
167
|
+
let p2 = p.id;
|
|
168
|
+
let p3 = p.description;
|
|
169
|
+
let _g3 = [];
|
|
170
|
+
let _g4 = 0;
|
|
171
|
+
let _g5 = p.options;
|
|
172
|
+
while (_g4 < _g5.length) _g3.push(Whet_Fields_.serializeOption(_g5[_g4++]));
|
|
173
|
+
_g.push({"name": p1, "id": p2, "description": p3, "options": _g3});
|
|
174
|
+
};
|
|
175
|
+
let _g3 = [];
|
|
176
|
+
let _g4 = 0;
|
|
177
|
+
let _g5 = Whet_Fields_.program.commands;
|
|
178
|
+
while (_g4 < _g5.length) {
|
|
179
|
+
let cmd = _g5[_g4];
|
|
180
|
+
++_g4;
|
|
181
|
+
let x = cmd.name();
|
|
182
|
+
if (!excludeCommands.includes(x)) {
|
|
183
|
+
_g3.push(Whet_Fields_.serializeCommand(cmd));
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
process.stdout.write(JSON.stringify({"projects": _g, "commands": _g3}, null, " ") + "\n");
|
|
187
|
+
}
|
|
188
|
+
static serializeOption(opt) {
|
|
189
|
+
return {"name": opt.name(), "attributeName": opt.attributeName(), "flags": opt.flags, "description": opt.description, "choices": opt.argChoices, "defaultValue": opt.defaultValue, "required": opt.required, "mandatory": opt.mandatory, "boolean": opt.isBoolean(), "hidden": opt.hidden};
|
|
190
|
+
}
|
|
191
|
+
static serializeCommand(cmd) {
|
|
192
|
+
let tmp = cmd.name();
|
|
193
|
+
let tmp1 = cmd.description();
|
|
194
|
+
let tmp2 = cmd.aliases();
|
|
195
|
+
let _g = [];
|
|
196
|
+
let _g1 = 0;
|
|
197
|
+
let _g2 = cmd.options;
|
|
198
|
+
while (_g1 < _g2.length) _g.push(Whet_Fields_.serializeOption(_g2[_g1++]));
|
|
199
|
+
return {"name": tmp, "description": tmp1, "aliases": tmp2, "options": _g};
|
|
200
|
+
}
|
|
153
201
|
static getCommands(args) {
|
|
154
202
|
let commands = [];
|
|
155
203
|
let from = 0;
|
|
@@ -27,6 +27,11 @@ export declare class BaseCache<Key, Value extends {
|
|
|
27
27
|
protected shouldKeep(stone: AnyStone, val: Value, durability: CacheDurability, useIndex: ((arg0: Value) => number), ageIndex: ((arg0: Value) => number)): boolean
|
|
28
28
|
protected setRecentUseOrder(values: Value[], value: Value): boolean
|
|
29
29
|
protected remove(stone: AnyStone, value: Value): Promise<any>
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
Remove all cached entries for a stone.
|
|
33
|
+
*/
|
|
34
|
+
clearStone(stone: AnyStone): void
|
|
30
35
|
protected key(stone: AnyStone): Key
|
|
31
36
|
protected value(source: Source): Promise<Value>
|
|
32
37
|
protected source(stone: AnyStone, value: Value): Promise<Source>
|
|
@@ -361,6 +361,13 @@ class BaseCache extends Register.inherits() {
|
|
|
361
361
|
HxOverrides.remove(this.cache.get(this.key(stone)), value);
|
|
362
362
|
return Promise.resolve(null);
|
|
363
363
|
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
Remove all cached entries for a stone.
|
|
367
|
+
*/
|
|
368
|
+
clearStone(stone) {
|
|
369
|
+
this.cache.remove(this.key(stone));
|
|
370
|
+
}
|
|
364
371
|
toString() {
|
|
365
372
|
let c = Boot.getClass(this);
|
|
366
373
|
return c.__name__;
|
|
@@ -31,5 +31,10 @@ export declare class CacheManager {
|
|
|
31
31
|
* The path is not reserved. Caching depends on stone's `cacheStrategy` and success of source generation.
|
|
32
32
|
*/
|
|
33
33
|
getDir(stone: AnyStone, hash?: null | SourceHash): string
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
Remove all cached entries for a stone. Opt-in – not called automatically by Project.removeStone.
|
|
37
|
+
*/
|
|
38
|
+
clearStone(stone: AnyStone): void
|
|
34
39
|
close(): Promise<void>
|
|
35
40
|
}
|
|
@@ -233,6 +233,14 @@ class CacheManager extends Register.inherits() {
|
|
|
233
233
|
};
|
|
234
234
|
return id;
|
|
235
235
|
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
Remove all cached entries for a stone. Opt-in – not called automatically by Project.removeStone.
|
|
239
|
+
*/
|
|
240
|
+
clearStone(stone) {
|
|
241
|
+
this.memCache.clearStone(stone);
|
|
242
|
+
this.fileCache.clearStone(stone);
|
|
243
|
+
}
|
|
236
244
|
close() {
|
|
237
245
|
return this.fileCache.close();
|
|
238
246
|
}
|
|
@@ -15,6 +15,7 @@ export declare class FileCache extends BaseCache<string, RuntimeFileCacheValue>
|
|
|
15
15
|
protected set(source: Source): Promise<RuntimeFileCacheValue>
|
|
16
16
|
protected getExistingDirs(stone: AnyStone): string[]
|
|
17
17
|
protected remove(stone: AnyStone, value: RuntimeFileCacheValue): Promise<any>
|
|
18
|
+
clearStone(stone: AnyStone): void
|
|
18
19
|
protected setRecentUseOrder(values: RuntimeFileCacheValue[], value: RuntimeFileCacheValue): boolean
|
|
19
20
|
protected getDirFor(value: RuntimeFileCacheValue): string
|
|
20
21
|
protected hasSourceId(value: RuntimeFileCacheValue, sourceId: string): boolean
|
|
@@ -220,6 +220,10 @@ class FileCache extends Register.inherits(() => BaseCache, true) {
|
|
|
220
220
|
});
|
|
221
221
|
});
|
|
222
222
|
}
|
|
223
|
+
clearStone(stone) {
|
|
224
|
+
super.clearStone(stone);
|
|
225
|
+
this.flush();
|
|
226
|
+
}
|
|
223
227
|
setRecentUseOrder(values, value) {
|
|
224
228
|
let changed = super.setRecentUseOrder(values, value);
|
|
225
229
|
if (changed) {
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {AnyStone} from "../Stone"
|
|
2
|
+
import {SourceHash} from "../SourceHash"
|
|
3
|
+
import {Source} from "../Source"
|
|
4
|
+
import {AsyncLocalStorage} from "node:async_hooks"
|
|
5
|
+
|
|
6
|
+
export declare class MemoContext {
|
|
7
|
+
constructor()
|
|
8
|
+
protected sources: Map<AnyStone, Promise<Source>>
|
|
9
|
+
protected hashes: Map<AnyStone, Promise<SourceHash>>
|
|
10
|
+
protected partials: Map<AnyStone, Map<string, Promise<null | Source>>>
|
|
11
|
+
protected contexts: Map<AnyStone, Promise<any>>
|
|
12
|
+
protected static als: AsyncLocalStorage<MemoContext>
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
Execute fn within a MemoContext. Reuses existing context or creates a new one.
|
|
16
|
+
*/
|
|
17
|
+
static ensure<T>(fn: (() => T)): T
|
|
18
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {AsyncLocalStorage} from "node:async_hooks"
|
|
2
|
+
import {Register} from "../../genes/Register.js"
|
|
3
|
+
|
|
4
|
+
const $global = Register.$global
|
|
5
|
+
|
|
6
|
+
export const MemoContext = Register.global("$hxClasses")["whet.cache.MemoContext"] =
|
|
7
|
+
class MemoContext extends Register.inherits() {
|
|
8
|
+
[Register.new]() {
|
|
9
|
+
this.contexts = new Map();
|
|
10
|
+
this.partials = new Map();
|
|
11
|
+
this.hashes = new Map();
|
|
12
|
+
this.sources = new Map();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
Execute fn within a MemoContext. Reuses existing context or creates a new one.
|
|
17
|
+
*/
|
|
18
|
+
static ensure(fn) {
|
|
19
|
+
if (MemoContext.als.getStore() != null) {
|
|
20
|
+
return fn();
|
|
21
|
+
};
|
|
22
|
+
return MemoContext.als.run(new MemoContext(), fn);
|
|
23
|
+
}
|
|
24
|
+
static get __name__() {
|
|
25
|
+
return "whet.cache.MemoContext"
|
|
26
|
+
}
|
|
27
|
+
get __class__() {
|
|
28
|
+
return MemoContext
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
MemoContext.prototype.sources = null;
|
|
32
|
+
MemoContext.prototype.hashes = null;
|
|
33
|
+
MemoContext.prototype.partials = null;
|
|
34
|
+
MemoContext.prototype.contexts = null;
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
MemoContext.als = new AsyncLocalStorage()
|
|
@@ -11,7 +11,7 @@ export const RoutePathType_Fields_ = Register.global("$hxClasses")["whet.magic._
|
|
|
11
11
|
class RoutePathType_Fields_ {
|
|
12
12
|
static makeRoutePath(routerPathType) {
|
|
13
13
|
if (((routerPathType) instanceof Router) || ((routerPathType) instanceof Stone) || typeof(routerPathType) == "string") {
|
|
14
|
-
return [{"routeUnder": "", "source": (typeof(routerPathType) == "string") ?
|
|
14
|
+
return [{"routeUnder": "", "source": (typeof(routerPathType) == "string") ? Files.fromPath(routerPathType) : routerPathType, "filter": null, "extractDirs": null}];
|
|
15
15
|
};
|
|
16
16
|
if (!((routerPathType) instanceof Array)) {
|
|
17
17
|
throw new Error("RoutePath should be a Stone, Router, or an array.");
|
|
@@ -23,7 +23,7 @@ class RoutePathType_Fields_ {
|
|
|
23
23
|
let item = _g2[_g1];
|
|
24
24
|
++_g1;
|
|
25
25
|
if (((item) instanceof Router) || ((item) instanceof Stone) || typeof(item) == "string") {
|
|
26
|
-
_g.push({"routeUnder": "", "source": (typeof(item) == "string") ?
|
|
26
|
+
_g.push({"routeUnder": "", "source": (typeof(item) == "string") ? Files.fromPath(item) : item, "filter": null, "extractDirs": null});
|
|
27
27
|
} else if (((item) instanceof Array)) {
|
|
28
28
|
let inner = item;
|
|
29
29
|
if (typeof(inner[0]) != "string") {
|
|
@@ -36,12 +36,12 @@ class RoutePathType_Fields_ {
|
|
|
36
36
|
switch (inner.length) {
|
|
37
37
|
case 2:
|
|
38
38
|
let src = inner[1];
|
|
39
|
-
let tmp1 = (typeof(src) == "string") ?
|
|
39
|
+
let tmp1 = (typeof(src) == "string") ? Files.fromPath(src) : src;
|
|
40
40
|
tmp = {"routeUnder": inner[0], "source": tmp1, "filter": null, "extractDirs": null};
|
|
41
41
|
break
|
|
42
42
|
case 3:
|
|
43
43
|
let src1 = inner[1];
|
|
44
|
-
let tmp2 = (typeof(src1) == "string") ?
|
|
44
|
+
let tmp2 = (typeof(src1) == "string") ? Files.fromPath(src1) : src1;
|
|
45
45
|
if (!(typeof(inner[2]) == "string" || inner[2] instanceof Minimatch)) {
|
|
46
46
|
throw new Error("Third" + " element of RoutePath array should be a glob pattern (string or `minimatch` object)");
|
|
47
47
|
};
|
|
@@ -50,7 +50,7 @@ class RoutePathType_Fields_ {
|
|
|
50
50
|
break
|
|
51
51
|
case 4:
|
|
52
52
|
let src2 = inner[1];
|
|
53
|
-
let tmp4 = (typeof(src2) == "string") ?
|
|
53
|
+
let tmp4 = (typeof(src2) == "string") ? Files.fromPath(src2) : src2;
|
|
54
54
|
if (!(typeof(inner[2]) == "string" || inner[2] instanceof Minimatch)) {
|
|
55
55
|
throw new Error("Third" + " element of RoutePath array should be a glob pattern (string or `minimatch` object)");
|
|
56
56
|
};
|
|
@@ -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
|
*/
|
|
@@ -11,6 +11,11 @@ export declare class Router {
|
|
|
11
11
|
protected routes: RoutePath[]
|
|
12
12
|
route(r: RoutePathType): void
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
Remove all routes.
|
|
16
|
+
*/
|
|
17
|
+
clearRoutes(): void
|
|
18
|
+
|
|
14
19
|
/**
|
|
15
20
|
Recursively collect all Stone IDs referenced in this Router's routes.
|
|
16
21
|
*/
|
|
@@ -29,6 +34,19 @@ export declare class Router {
|
|
|
29
34
|
*/
|
|
30
35
|
getHash(pattern?: null | MinimatchType): Promise<SourceHash>
|
|
31
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
|
+
|
|
32
50
|
/**
|
|
33
51
|
* Save files filtered by `pattern` into provided `saveInto` folder.
|
|
34
52
|
*/
|
package/bin/whet/route/Router.js
CHANGED
|
@@ -2,6 +2,7 @@ import {RouteResult} from "./RouteResult.js"
|
|
|
2
2
|
import {OutputFilterMatcher} from "./OutputFilterMatcher.js"
|
|
3
3
|
import {RoutePathType_Fields_} from "../magic/RoutePathType.js"
|
|
4
4
|
import {MinimatchType_Fields_} from "../magic/MinimatchType.js"
|
|
5
|
+
import {MemoContext} from "../cache/MemoContext.js"
|
|
5
6
|
import {Utils} from "../Utils.js"
|
|
6
7
|
import {Stone} from "../Stone.js"
|
|
7
8
|
import {SourceHash} from "../SourceHash.js"
|
|
@@ -21,6 +22,13 @@ class Router extends Register.inherits() {
|
|
|
21
22
|
while (_g < _g1.length) this.routes.push(_g1[_g++]);
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
/**
|
|
26
|
+
Remove all routes.
|
|
27
|
+
*/
|
|
28
|
+
clearRoutes() {
|
|
29
|
+
this.routes.length = 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
/**
|
|
25
33
|
Recursively collect all Stone IDs referenced in this Router's routes.
|
|
26
34
|
*/
|
|
@@ -56,7 +64,10 @@ class Router extends Register.inherits() {
|
|
|
56
64
|
* @param pattern A glob pattern to search for.
|
|
57
65
|
*/
|
|
58
66
|
get(pattern) {
|
|
59
|
-
|
|
67
|
+
let _gthis = this;
|
|
68
|
+
return MemoContext.ensure(function () {
|
|
69
|
+
return _gthis.getResults([{"pathSoFar": [], "filter": (pattern != null) ? MinimatchType_Fields_.makeMinimatch(pattern) : null, "inProgress": true, "remDirs": []}], []);
|
|
70
|
+
});
|
|
60
71
|
}
|
|
61
72
|
getResults(mainFilters, results) {
|
|
62
73
|
let _gthis = this;
|
|
@@ -207,46 +218,99 @@ class Router extends Register.inherits() {
|
|
|
207
218
|
* Includes matched serveIds in hash to capture filter effects.
|
|
208
219
|
*/
|
|
209
220
|
getHash(pattern) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
while (_g < items.length) {
|
|
215
|
-
let item = items[_g];
|
|
216
|
-
++_g;
|
|
217
|
-
if (uniqueStones.indexOf(item.source) == -1) {
|
|
218
|
-
uniqueStones.push(item.source);
|
|
219
|
-
};
|
|
220
|
-
serveIds.push(item.serveId);
|
|
221
|
-
};
|
|
222
|
-
serveIds.sort(function (a, b) {
|
|
223
|
-
if (a < b) {
|
|
224
|
-
return -1;
|
|
225
|
-
} else if (a > b) {
|
|
226
|
-
return 1;
|
|
227
|
-
} else {
|
|
228
|
-
return 0;
|
|
229
|
-
};
|
|
221
|
+
let _gthis = this;
|
|
222
|
+
if (pattern == null) {
|
|
223
|
+
return MemoContext.ensure(function () {
|
|
224
|
+
return _gthis.getUnfilteredHash();
|
|
230
225
|
});
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
let
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
if (
|
|
226
|
+
};
|
|
227
|
+
return MemoContext.ensure(function () {
|
|
228
|
+
return _gthis.get(pattern).then(function (items) {
|
|
229
|
+
let uniqueStones = [];
|
|
230
|
+
let serveIds = [];
|
|
231
|
+
let _g = 0;
|
|
232
|
+
while (_g < items.length) {
|
|
233
|
+
let item = items[_g];
|
|
234
|
+
++_g;
|
|
235
|
+
if (uniqueStones.indexOf(item.source) == -1) {
|
|
236
|
+
uniqueStones.push(item.source);
|
|
237
|
+
};
|
|
238
|
+
serveIds.push(item.serveId);
|
|
239
|
+
};
|
|
240
|
+
serveIds.sort(function (a, b) {
|
|
241
|
+
if (a < b) {
|
|
241
242
|
return -1;
|
|
242
|
-
} else if (a
|
|
243
|
+
} else if (a > b) {
|
|
243
244
|
return 1;
|
|
244
245
|
} else {
|
|
245
246
|
return 0;
|
|
246
247
|
};
|
|
247
248
|
});
|
|
248
|
-
|
|
249
|
+
let result = new Array(uniqueStones.length);
|
|
250
|
+
let _g1 = 0;
|
|
251
|
+
let _g2 = uniqueStones.length;
|
|
252
|
+
while (_g1 < _g2) {
|
|
253
|
+
let i = _g1++;
|
|
254
|
+
result[i] = uniqueStones[i].getHash();
|
|
255
|
+
};
|
|
256
|
+
return Promise.all(result).then(function (hashes) {
|
|
257
|
+
hashes.sort(function (a, b) {
|
|
258
|
+
if (a.toString() < b.toString()) {
|
|
259
|
+
return -1;
|
|
260
|
+
} else if (a.toString() > b.toString()) {
|
|
261
|
+
return 1;
|
|
262
|
+
} else {
|
|
263
|
+
return 0;
|
|
264
|
+
};
|
|
265
|
+
});
|
|
266
|
+
return SourceHash.merge(...hashes).add(SourceHash.fromString(serveIds.join("\n")));
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
}
|
|
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
|
+
};
|
|
249
312
|
});
|
|
313
|
+
return SourceHash.merge(...tokens);
|
|
250
314
|
});
|
|
251
315
|
}
|
|
252
316
|
|
|
@@ -18,6 +18,11 @@ export declare class Files extends Stone<FilesConfig> {
|
|
|
18
18
|
id: string,
|
|
19
19
|
pathId: string
|
|
20
20
|
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
Reuse an existing Files stone for this path, or create a new one.
|
|
24
|
+
*/
|
|
25
|
+
static fromPath(path: string): Files
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
export type FilesConfig = {
|
package/bin/whet/stones/Files.js
CHANGED
|
@@ -5,6 +5,7 @@ import {Stone} from "../Stone.js"
|
|
|
5
5
|
import {IdUtils, RootDir} from "../SourceId.js"
|
|
6
6
|
import {SourceHash} from "../SourceHash.js"
|
|
7
7
|
import {SourceData} from "../Source.js"
|
|
8
|
+
import {Project} from "../Project.js"
|
|
8
9
|
import {Register} from "../../genes/Register.js"
|
|
9
10
|
|
|
10
11
|
const $global = Register.$global
|
|
@@ -164,6 +165,25 @@ class Files extends Register.inherits(() => Stone, true) {
|
|
|
164
165
|
};
|
|
165
166
|
return {"pathId": pathId, "id": tmp};
|
|
166
167
|
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
Reuse an existing Files stone for this path, or create a new one.
|
|
171
|
+
*/
|
|
172
|
+
static fromPath(path) {
|
|
173
|
+
let project = Project.projects[Project.projects.length - 1];
|
|
174
|
+
if (project != null) {
|
|
175
|
+
let _g = 0;
|
|
176
|
+
let _g1 = project.stones;
|
|
177
|
+
while (_g < _g1.length) {
|
|
178
|
+
let stone = _g1[_g];
|
|
179
|
+
++_g;
|
|
180
|
+
if (stone.id == path && ((stone) instanceof Files)) {
|
|
181
|
+
return stone;
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
return new Files({"paths": [path]});
|
|
186
|
+
}
|
|
167
187
|
static get __name__() {
|
|
168
188
|
return "whet.stones.Files"
|
|
169
189
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {Router} from "../route/Router"
|
|
2
|
+
import {RoutePathType} from "../magic/RoutePathType"
|
|
3
|
+
import {AnyStone} from "../Stone"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Base class for dynamic stone lifecycle management.
|
|
7
|
+
*
|
|
8
|
+
* Manages creation, update, and removal of stones based on external data.
|
|
9
|
+
* Extends Router so it can be used directly as a route source.
|
|
10
|
+
*
|
|
11
|
+
* JS subclasses override `createEntry`, `updateEntry`, and `addBaseRoutes`.
|
|
12
|
+
*/
|
|
13
|
+
export declare class StoneFactory<D, E extends FactoryEntry> extends Router {
|
|
14
|
+
constructor(routes?: null | RoutePathType)
|
|
15
|
+
entryMap: Map<string, E>
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Sync factory state with data. Calls `createEntry` for new keys,
|
|
19
|
+
* `updateEntry` for existing keys, and removes entries whose keys are absent.
|
|
20
|
+
* @return Array of removed keys (caller can use for cache cleanup via `cache.clearStone`).
|
|
21
|
+
*/
|
|
22
|
+
sync(data: D[], keyFn: ((arg0: D) => string)): string[]
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
Override in subclass: create stones and routes for a new data entry.
|
|
26
|
+
*/
|
|
27
|
+
protected createEntry(key: string, data: D): E
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Override in subclass: update an existing entry's stone configs in-place.
|
|
31
|
+
* Default implementation destroys and recreates the entry.
|
|
32
|
+
*/
|
|
33
|
+
protected updateEntry(key: string, data: D, existing: E): void
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
Override in subclass: add routes that are present on every sync (e.g., a database JSON file).
|
|
37
|
+
*/
|
|
38
|
+
protected addBaseRoutes(): void
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
Remove an entry, deregistering its stones from the project.
|
|
42
|
+
*/
|
|
43
|
+
removeEntry(key: string): void
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type FactoryEntry = {
|
|
47
|
+
/**
|
|
48
|
+
Route definitions for this entry — added to the factory's Router on sync.
|
|
49
|
+
*/
|
|
50
|
+
routes?: null | RoutePathType,
|
|
51
|
+
/**
|
|
52
|
+
Stones owned by this entry — deregistered from project on removal.
|
|
53
|
+
*/
|
|
54
|
+
stones: AnyStone[]
|
|
55
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import {Router} from "../route/Router.js"
|
|
2
|
+
import {RoutePathType_Fields_} from "../magic/RoutePathType.js"
|
|
3
|
+
import {Register} from "../../genes/Register.js"
|
|
4
|
+
|
|
5
|
+
const $global = Register.$global
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Base class for dynamic stone lifecycle management.
|
|
9
|
+
*
|
|
10
|
+
* Manages creation, update, and removal of stones based on external data.
|
|
11
|
+
* Extends Router so it can be used directly as a route source.
|
|
12
|
+
*
|
|
13
|
+
* JS subclasses override `createEntry`, `updateEntry`, and `addBaseRoutes`.
|
|
14
|
+
*/
|
|
15
|
+
export const StoneFactory = Register.global("$hxClasses")["whet.stones.StoneFactory"] =
|
|
16
|
+
class StoneFactory extends Register.inherits(Router) {
|
|
17
|
+
[Register.new](routes) {
|
|
18
|
+
this.entryMap = new Map();
|
|
19
|
+
super[Register.new](routes);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Sync factory state with data. Calls `createEntry` for new keys,
|
|
24
|
+
* `updateEntry` for existing keys, and removes entries whose keys are absent.
|
|
25
|
+
* @return Array of removed keys (caller can use for cache cleanup via `cache.clearStone`).
|
|
26
|
+
*/
|
|
27
|
+
sync(data, keyFn) {
|
|
28
|
+
let newKeys = new Set();
|
|
29
|
+
this.clearRoutes();
|
|
30
|
+
this.addBaseRoutes();
|
|
31
|
+
let _g = 0;
|
|
32
|
+
while (_g < data.length) {
|
|
33
|
+
let item = data[_g];
|
|
34
|
+
++_g;
|
|
35
|
+
let key = keyFn(item);
|
|
36
|
+
newKeys.add(key);
|
|
37
|
+
if (this.entryMap.has(key)) {
|
|
38
|
+
this.updateEntry(key, item, this.entryMap.get(key));
|
|
39
|
+
} else {
|
|
40
|
+
this.entryMap.set(key, this.createEntry(key, item));
|
|
41
|
+
};
|
|
42
|
+
let entry = this.entryMap.get(key);
|
|
43
|
+
if (entry.routes != null) {
|
|
44
|
+
let _g = 0;
|
|
45
|
+
let _g1 = RoutePathType_Fields_.makeRoutePath(entry.routes);
|
|
46
|
+
while (_g < _g1.length) this.routes.push(_g1[_g++]);
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
let toRemove = [];
|
|
50
|
+
let jsIterator = this.entryMap.keys();
|
|
51
|
+
let _g_lastStep = jsIterator.next();
|
|
52
|
+
while (!_g_lastStep.done) {
|
|
53
|
+
let v = _g_lastStep.value;
|
|
54
|
+
_g_lastStep = jsIterator.next();
|
|
55
|
+
if (!newKeys.has(v)) {
|
|
56
|
+
toRemove.push(v);
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
let _g1 = 0;
|
|
60
|
+
while (_g1 < toRemove.length) this.removeEntry(toRemove[_g1++]);
|
|
61
|
+
return toRemove;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
Override in subclass: create stones and routes for a new data entry.
|
|
66
|
+
*/
|
|
67
|
+
createEntry(key, data) {
|
|
68
|
+
throw new Error("StoneFactory.createEntry must be overridden");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Override in subclass: update an existing entry's stone configs in-place.
|
|
73
|
+
* Default implementation destroys and recreates the entry.
|
|
74
|
+
*/
|
|
75
|
+
updateEntry(key, data, existing) {
|
|
76
|
+
this.removeEntry(key);
|
|
77
|
+
this.entryMap.set(key, this.createEntry(key, data));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
Override in subclass: add routes that are present on every sync (e.g., a database JSON file).
|
|
82
|
+
*/
|
|
83
|
+
addBaseRoutes() {
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
Remove an entry, deregistering its stones from the project.
|
|
88
|
+
*/
|
|
89
|
+
removeEntry(key) {
|
|
90
|
+
let entry = this.entryMap.get(key);
|
|
91
|
+
if (entry == null) {
|
|
92
|
+
return;
|
|
93
|
+
};
|
|
94
|
+
if (entry.stones != null) {
|
|
95
|
+
let stones = entry.stones;
|
|
96
|
+
let _g = 0;
|
|
97
|
+
while (_g < stones.length) {
|
|
98
|
+
let s = stones[_g];
|
|
99
|
+
++_g;
|
|
100
|
+
s.project.removeStone(s);
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
this.entryMap["delete"](key);
|
|
104
|
+
}
|
|
105
|
+
static get __name__() {
|
|
106
|
+
return "whet.stones.StoneFactory"
|
|
107
|
+
}
|
|
108
|
+
static get __super__() {
|
|
109
|
+
return Router
|
|
110
|
+
}
|
|
111
|
+
get __class__() {
|
|
112
|
+
return StoneFactory
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
StoneFactory.prototype.entryMap = null;
|
|
116
|
+
|
package/bin/whet.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export {addOption} from "./whet/Project"
|
|
|
3
3
|
export {ZipStone} from "./whet/stones/Zip"
|
|
4
4
|
export {ZipConfig} from "./whet/stones/Zip"
|
|
5
5
|
export {Utils} from "./whet/Utils"
|
|
6
|
+
export {StoneFactory} from "./whet/stones/StoneFactory"
|
|
6
7
|
export {Stone} from "./whet/Stone"
|
|
7
8
|
export {Router} from "./whet/route/Router"
|
|
8
9
|
export {RemoteFileConfig} from "./whet/stones/RemoteFile"
|
|
@@ -17,6 +18,7 @@ export {Hxml} from "./whet/stones/haxe/Hxml"
|
|
|
17
18
|
export {HaxeBuild} from "./whet/stones/haxe/HaxeBuild"
|
|
18
19
|
export {FilesConfig} from "./whet/stones/Files"
|
|
19
20
|
export {Files} from "./whet/stones/Files"
|
|
21
|
+
export {FactoryEntry} from "./whet/stones/StoneFactory"
|
|
20
22
|
export {DCE} from "./whet/stones/haxe/Hxml"
|
|
21
23
|
export {ConfigStore} from "./whet/ConfigStore"
|
|
22
24
|
export {BuildPlatform} from "./whet/stones/haxe/Hxml"
|
package/bin/whet.js
CHANGED
|
@@ -8,6 +8,7 @@ Whet_Fields_.main()
|
|
|
8
8
|
export {addOption} from "./whet/Project.js"
|
|
9
9
|
export {ZipStone} from "./whet/stones/Zip.js"
|
|
10
10
|
export {Utils} from "./whet/Utils.js"
|
|
11
|
+
export {StoneFactory} from "./whet/stones/StoneFactory.js"
|
|
11
12
|
export {Stone} from "./whet/Stone.js"
|
|
12
13
|
export {Router} from "./whet/route/Router.js"
|
|
13
14
|
export {RemoteFile} from "./whet/stones/RemoteFile.js"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whet",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "NodeJS based assets management and project tooling library.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"devinit": "npx dts2hx commander pino-pretty minimatch --modular --noLibWrap",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"homepage": "https://github.com/Antriel/whet#readme",
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"commander": "^14.0.3",
|
|
30
|
-
"minimatch": "^10.2.
|
|
30
|
+
"minimatch": "^10.2.5",
|
|
31
31
|
"pino-pretty": "^13.1.3"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|