whet 0.2.0 → 0.4.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.
@@ -6,6 +6,7 @@ export declare class EsMap<K, V> {
6
6
  protected inst: Map<K, V>
7
7
  set(key: K, value: V): void
8
8
  get(key: K): null | V
9
+ remove(key: K): boolean
9
10
  exists(key: K): boolean
10
11
  keys(): Iterator<K>
11
12
  protected static adaptIterator<T>(from: Iterator__1<T>): Iterator<T>
@@ -13,6 +13,9 @@ class EsMap extends Register.inherits() {
13
13
  get(key) {
14
14
  return this.inst.get(key);
15
15
  }
16
+ remove(key) {
17
+ return this.inst["delete"](key);
18
+ }
16
19
  exists(key) {
17
20
  return this.inst.has(key);
18
21
  }
@@ -4,5 +4,6 @@ export declare interface IMap<K, V> {
4
4
  get(k: K): null | V
5
5
  set(k: K, v: V): void
6
6
  exists(k: K): boolean
7
+ remove(k: K): boolean
7
8
  keys(): Iterator<K>
8
9
  }
package/bin/whet/Log.js CHANGED
@@ -89,6 +89,9 @@ class LogLevel {
89
89
  case "info":
90
90
  return 30;
91
91
  break
92
+ case "silent":
93
+ return 100;
94
+ break
92
95
  case "trace":
93
96
  return 10;
94
97
  break
@@ -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>
@@ -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;
@@ -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.**
@@ -127,6 +128,7 @@ export declare class Stone<T extends StoneConfig> {
127
128
  * Uses the same cache as getSource() — partial and full share the pool.
128
129
  */
129
130
  getPartialSource(sourceId: string): Promise<null | Source>
131
+ protected _computePartialSource(sourceId: string): Promise<null | Source>
130
132
 
131
133
  /**
132
134
  * 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"
@@ -116,7 +117,24 @@ class Stone extends Register.inherits() {
116
117
  */
117
118
  getSource() {
118
119
  Log.log(20, ...["Getting source.", {"stone": this}]);
119
- return this.project.cache.getSource(this);
120
+ let ctx = MemoContext.als.getStore();
121
+ if (ctx != null) {
122
+ let cached = ctx.sources.get(this);
123
+ if (cached != null) {
124
+ Log.log(10, ...["Source memo hit.", {"stone": this}]);
125
+ return cached;
126
+ };
127
+ let p = this.project.cache.getSource(this);
128
+ ctx.sources.set(this, p);
129
+ return p;
130
+ };
131
+ let _gthis = this;
132
+ return MemoContext.ensure(function () {
133
+ let newCtx = MemoContext.als.getStore();
134
+ let p = _gthis.project.cache.getSource(_gthis);
135
+ newCtx.sources.set(_gthis, p);
136
+ return p;
137
+ });
120
138
  }
121
139
 
122
140
  /**
@@ -126,6 +144,44 @@ class Stone extends Register.inherits() {
126
144
  */
127
145
  getHash() {
128
146
  Log.log(20, ...["Generating hash.", {"stone": this}]);
147
+ let ctx = MemoContext.als.getStore();
148
+ if (ctx != null) {
149
+ let cached = ctx.hashes.get(this);
150
+ if (cached != null) {
151
+ Log.log(10, ...["Hash memo hit.", {"stone": this}]);
152
+ return cached;
153
+ };
154
+ let _gthis = this;
155
+ let p = this.finalMaybeHash().then(function (hash) {
156
+ if (hash != null) {
157
+ return hash;
158
+ } else {
159
+ return _gthis.getSource().then(function (s) {
160
+ return s.hash;
161
+ });
162
+ };
163
+ });
164
+ ctx.hashes.set(this, p);
165
+ return p;
166
+ };
167
+ let _gthis = this;
168
+ return MemoContext.ensure(function () {
169
+ let newCtx = MemoContext.als.getStore();
170
+ let _gthis1 = _gthis;
171
+ let p = _gthis.finalMaybeHash().then(function (hash) {
172
+ if (hash != null) {
173
+ return hash;
174
+ } else {
175
+ return _gthis1.getSource().then(function (s) {
176
+ return s.hash;
177
+ });
178
+ };
179
+ });
180
+ newCtx.hashes.set(_gthis, p);
181
+ return p;
182
+ });
183
+ }
184
+ _computeHash() {
129
185
  let _gthis = this;
130
186
  return this.finalMaybeHash().then(function (hash) {
131
187
  if (hash != null) {
@@ -347,6 +403,41 @@ class Stone extends Register.inherits() {
347
403
  * Uses the same cache as getSource() — partial and full share the pool.
348
404
  */
349
405
  getPartialSource(sourceId) {
406
+ let ctx = MemoContext.als.getStore();
407
+ if (ctx != null) {
408
+ let partialMap = ctx.partials.get(this);
409
+ if (partialMap != null) {
410
+ let cached = partialMap.get(sourceId);
411
+ if (cached != null) {
412
+ Log.log(10, ...["Partial source memo hit.", {"stone": this, "sourceId": sourceId}]);
413
+ return cached;
414
+ };
415
+ };
416
+ let fullCached = ctx.sources.get(this);
417
+ if (fullCached != null) {
418
+ return fullCached.then(function (s) {
419
+ return s.filterTo(sourceId);
420
+ });
421
+ };
422
+ let p = this._computePartialSource(sourceId);
423
+ if (partialMap == null) {
424
+ partialMap = new Map();
425
+ ctx.partials.set(this, partialMap);
426
+ };
427
+ partialMap.set(sourceId, p);
428
+ return p;
429
+ };
430
+ let _gthis = this;
431
+ return MemoContext.ensure(function () {
432
+ let newCtx = MemoContext.als.getStore();
433
+ let p = _gthis._computePartialSource(sourceId);
434
+ let partialMap = new Map();
435
+ newCtx.partials.set(_gthis, partialMap);
436
+ partialMap.set(sourceId, p);
437
+ return p;
438
+ });
439
+ }
440
+ _computePartialSource(sourceId) {
350
441
  let _gthis = this;
351
442
  return this.finalMaybeHash().then(function (hash) {
352
443
  if (hash == null) {
@@ -1,4 +1,4 @@
1
- import {Command} from "commander"
1
+ import {Command, Option} from "commander"
2
2
 
3
3
  export const program: Command
4
4
  export const main: () => void
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.2.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();
29
+ Whet_Fields_.program.enablePositionalOptions().passThroughOptions().description("Project tooling.").usage("[options] [command] [+ [command]...]").version("0.4.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,17 @@
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 static als: AsyncLocalStorage<MemoContext>
12
+
13
+ /**
14
+ Execute fn within a MemoContext. Reuses existing context or creates a new one.
15
+ */
16
+ static ensure<T>(fn: (() => T)): T
17
+ }
@@ -0,0 +1,35 @@
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.partials = new Map();
10
+ this.hashes = new Map();
11
+ this.sources = new Map();
12
+ }
13
+
14
+ /**
15
+ Execute fn within a MemoContext. Reuses existing context or creates a new one.
16
+ */
17
+ static ensure(fn) {
18
+ if (MemoContext.als.getStore() != null) {
19
+ return fn();
20
+ };
21
+ return MemoContext.als.run(new MemoContext(), fn);
22
+ }
23
+ static get __name__() {
24
+ return "whet.cache.MemoContext"
25
+ }
26
+ get __class__() {
27
+ return MemoContext
28
+ }
29
+ }
30
+ MemoContext.prototype.sources = null;
31
+ MemoContext.prototype.hashes = null;
32
+ MemoContext.prototype.partials = null;
33
+
34
+
35
+ 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") ? new Files({"paths": [routerPathType]}) : routerPathType, "filter": null, "extractDirs": null}];
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") ? new Files({"paths": [item]}) : item, "filter": null, "extractDirs": null});
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") ? new Files({"paths": [src]}) : src;
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") ? new Files({"paths": [src1]}) : src1;
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") ? new Files({"paths": [src2]}) : src2;
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
  };
@@ -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
  */
@@ -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
- return this.getResults([{"pathSoFar": [], "filter": (pattern != null) ? MinimatchType_Fields_.makeMinimatch(pattern) : null, "inProgress": true, "remDirs": []}], []);
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,45 +218,48 @@ class Router extends Register.inherits() {
207
218
  * Includes matched serveIds in hash to capture filter effects.
208
219
  */
209
220
  getHash(pattern) {
210
- return this.get(pattern).then(function (items) {
211
- let uniqueStones = [];
212
- let serveIds = [];
213
- let _g = 0;
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;
221
+ let _gthis = this;
222
+ return MemoContext.ensure(function () {
223
+ return _gthis.get(pattern).then(function (items) {
224
+ let uniqueStones = [];
225
+ let serveIds = [];
226
+ let _g = 0;
227
+ while (_g < items.length) {
228
+ let item = items[_g];
229
+ ++_g;
230
+ if (uniqueStones.indexOf(item.source) == -1) {
231
+ uniqueStones.push(item.source);
232
+ };
233
+ serveIds.push(item.serveId);
229
234
  };
230
- });
231
- let result = new Array(uniqueStones.length);
232
- let _g1 = 0;
233
- let _g2 = uniqueStones.length;
234
- while (_g1 < _g2) {
235
- let i = _g1++;
236
- result[i] = uniqueStones[i].getHash();
237
- };
238
- return Promise.all(result).then(function (hashes) {
239
- hashes.sort(function (a, b) {
240
- if (a.toString() < b.toString()) {
235
+ serveIds.sort(function (a, b) {
236
+ if (a < b) {
241
237
  return -1;
242
- } else if (a.toString() > b.toString()) {
238
+ } else if (a > b) {
243
239
  return 1;
244
240
  } else {
245
241
  return 0;
246
242
  };
247
243
  });
248
- return SourceHash.merge(...hashes).add(SourceHash.fromString(serveIds.join("\n")));
244
+ let result = new Array(uniqueStones.length);
245
+ let _g1 = 0;
246
+ let _g2 = uniqueStones.length;
247
+ while (_g1 < _g2) {
248
+ let i = _g1++;
249
+ result[i] = uniqueStones[i].getHash();
250
+ };
251
+ return Promise.all(result).then(function (hashes) {
252
+ hashes.sort(function (a, b) {
253
+ if (a.toString() < b.toString()) {
254
+ return -1;
255
+ } else if (a.toString() > b.toString()) {
256
+ return 1;
257
+ } else {
258
+ return 0;
259
+ };
260
+ });
261
+ return SourceHash.merge(...hashes).add(SourceHash.fromString(serveIds.join("\n")));
262
+ });
249
263
  });
250
264
  });
251
265
  }
@@ -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 = {
@@ -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.2.0",
3
+ "version": "0.4.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.4",
30
+ "minimatch": "^10.2.5",
31
31
  "pino-pretty": "^13.1.3"
32
32
  },
33
33
  "devDependencies": {