@holoscript/std 3.1.1 → 7.0.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/LICENSE +21 -0
- package/dist/__tests__/EconomicPrimitives.test.d.ts +2 -0
- package/dist/__tests__/EconomicTraits.test.d.ts +2 -0
- package/dist/__tests__/SimulationLabPrimitives.test.d.ts +2 -0
- package/dist/__tests__/collections.test.d.ts +2 -0
- package/dist/__tests__/events.test.d.ts +2 -0
- package/dist/__tests__/index.test.d.ts +2 -0
- package/dist/__tests__/index.test.d.ts.map +1 -0
- package/dist/__tests__/materials.test.d.ts +2 -0
- package/dist/__tests__/math.test.d.ts +2 -0
- package/dist/__tests__/physics.test.d.ts +2 -0
- package/dist/__tests__/spatial.test.d.ts +2 -0
- package/dist/__tests__/standard-library-fundamentals.test.d.ts +2 -0
- package/dist/__tests__/standard-library-fundamentals.test.d.ts.map +1 -0
- package/dist/__tests__/string-and-time-utilities.test.d.ts +2 -0
- package/dist/__tests__/string-and-time-utilities.test.d.ts.map +1 -0
- package/dist/__tests__/string-decoupled.test.d.ts +2 -0
- package/dist/__tests__/string.test.d.ts +2 -0
- package/dist/__tests__/time.test.d.ts +2 -0
- package/dist/chunk-DZHAVOCH.js +686 -0
- package/dist/chunk-DZHAVOCH.js.map +1 -0
- package/dist/chunk-P4O3WNG2.js +287 -0
- package/dist/chunk-P4O3WNG2.js.map +1 -0
- package/dist/chunk-PFX2JM4X.js +683 -0
- package/dist/chunk-PFX2JM4X.js.map +1 -0
- package/dist/chunk-PR4QN5HX.js +43 -0
- package/dist/chunk-PR4QN5HX.js.map +1 -0
- package/dist/chunk-WS6W35YC.js +443 -0
- package/dist/chunk-WS6W35YC.js.map +1 -0
- package/dist/chunk-XBA4ARST.js +9313 -0
- package/dist/chunk-XBA4ARST.js.map +1 -0
- package/dist/chunk-XJIFG7G3.js +348 -0
- package/dist/chunk-XJIFG7G3.js.map +1 -0
- package/dist/chunk-ZXVZAQCZ.js +470 -0
- package/dist/chunk-ZXVZAQCZ.js.map +1 -0
- package/dist/collections.cjs +715 -0
- package/dist/collections.cjs.map +1 -0
- package/dist/collections.d.ts +177 -0
- package/dist/collections.js +18 -0
- package/dist/collections.js.map +1 -0
- package/dist/events.d.ts +32 -0
- package/dist/fs/__tests__/file-watch-system.test.d.ts +2 -0
- package/dist/fs/__tests__/file-watch-system.test.d.ts.map +1 -0
- package/dist/fs/__tests__/path-utilities.test.d.ts +2 -0
- package/dist/fs/__tests__/path-utilities.test.d.ts.map +1 -0
- package/dist/fs/__tests__/path.test.d.ts +2 -0
- package/dist/fs/__tests__/path.test.d.ts.map +1 -0
- package/dist/fs/fs.d.ts +288 -0
- package/dist/fs/fs.d.ts.map +1 -0
- package/dist/fs/index.cjs +9338 -0
- package/dist/fs/index.cjs.map +1 -0
- package/dist/fs/index.d.ts +51 -0
- package/dist/fs/index.d.ts.map +1 -0
- package/dist/fs/index.js +204 -0
- package/dist/fs/index.js.map +1 -0
- package/dist/fs/path.d.ts +135 -0
- package/dist/fs/path.d.ts.map +1 -0
- package/dist/fs/watch.d.ts +133 -0
- package/dist/fs/watch.d.ts.map +1 -0
- package/dist/index.cjs +13326 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +102 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1275 -190
- package/dist/index.js.map +1 -0
- package/dist/materials.d.ts +50 -0
- package/dist/math.cjs +595 -0
- package/dist/math.cjs.map +1 -0
- package/dist/math.d.ts +162 -0
- package/dist/math.js +54 -0
- package/dist/math.js.map +1 -0
- package/dist/physics.d.ts +59 -0
- package/dist/spatial.d.ts +82 -0
- package/dist/string.basic.test.d.ts +5 -0
- package/dist/string.basic.test.d.ts.map +1 -0
- package/dist/string.cjs +422 -0
- package/dist/string.cjs.map +1 -0
- package/dist/string.d.ts +212 -0
- package/dist/string.d.ts.map +1 -1
- package/dist/string.js +108 -0
- package/dist/string.js.map +1 -0
- package/dist/string.test.d.ts +5 -0
- package/dist/string.test.d.ts.map +1 -0
- package/dist/time.cjs +512 -0
- package/dist/time.cjs.map +1 -0
- package/dist/time.d.ts +215 -0
- package/dist/time.js +44 -0
- package/dist/time.js.map +1 -0
- package/dist/traits/ARTraits.d.ts +14 -0
- package/dist/traits/EconomicPrimitives.cjs +335 -0
- package/dist/traits/EconomicPrimitives.cjs.map +1 -0
- package/dist/traits/EconomicPrimitives.d.ts +345 -0
- package/dist/traits/EconomicPrimitives.js +56 -0
- package/dist/traits/EconomicPrimitives.js.map +1 -0
- package/dist/traits/EconomicTraits.cjs +471 -0
- package/dist/traits/EconomicTraits.cjs.map +1 -0
- package/dist/traits/EconomicTraits.d.ts +71 -0
- package/dist/traits/EconomicTraits.js +18 -0
- package/dist/traits/EconomicTraits.js.map +1 -0
- package/dist/traits/IoTTraits.d.ts +14 -0
- package/dist/traits/SimulationLabPrimitives.d.ts +187 -0
- package/dist/traits/SimulationLabTraits.d.ts +42 -0
- package/dist/traits/VRRTraits.d.ts +150 -0
- package/dist/traits/__tests__/ARTraits.test.d.ts +2 -0
- package/dist/traits/__tests__/ARTraits.test.d.ts.map +1 -0
- package/dist/traits/__tests__/EconomicPrimitives.test.d.ts +2 -0
- package/dist/traits/__tests__/EconomicPrimitives.test.d.ts.map +1 -0
- package/dist/traits/__tests__/EconomicTraits.test.d.ts +2 -0
- package/dist/traits/__tests__/EconomicTraits.test.d.ts.map +1 -0
- package/dist/types.d.ts +202 -0
- package/dist/types.d.ts.map +1 -1
- package/error-utf8.log +6 -0
- package/error.log +0 -0
- package/package.json +35 -18
- package/src/__tests__/index.test.ts +14 -0
- package/src/__tests__/{Sprint33.test.ts → standard-library-fundamentals.test.ts} +91 -74
- package/src/__tests__/{Sprint56.test.ts → string-and-time-utilities.test.ts} +64 -64
- package/src/__tests__/string-decoupled.test.ts +6 -9
- package/src/fs/__tests__/file-watch-system.test.ts +675 -0
- package/src/fs/__tests__/path-utilities.test.ts +375 -0
- package/src/fs/__tests__/path.test.ts +287 -0
- package/src/fs/fs.ts +692 -0
- package/src/fs/index.ts +194 -0
- package/src/fs/path.ts +310 -0
- package/src/fs/watch.ts +413 -0
- package/src/index.ts +3 -1
- package/src/math.ts +1 -1
- package/src/string.basic.test.ts +1 -1
- package/src/string.test.ts +4 -4
- package/src/string.ts +32 -17
- package/src/time.ts +1 -1
- package/src/traits/ARTraits.ts +2 -2
- package/src/traits/IoTTraits.ts +2 -2
- package/src/traits/VRRTraits.ts +2 -2
- package/src/traits/__tests__/ARTraits.test.ts +8 -0
- package/src/traits/__tests__/EconomicPrimitives.test.ts +14 -0
- package/src/traits/__tests__/EconomicTraits.test.ts +16 -0
- package/src/types.ts +10 -3
- package/std_error.log +5 -0
- package/tsup.config.ts +20 -0
- package/dist/__tests__/Sprint33.test.d.ts.map +0 -1
- package/dist/__tests__/Sprint56.test.d.ts.map +0 -1
package/src/fs/watch.ts
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @holoscript/fs - File Watch Module
|
|
3
|
+
*
|
|
4
|
+
* File and directory watching utilities for HoloScript Plus programs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chokidar, { type FSWatcher, type WatchOptions as ChokidarOptions } from 'chokidar';
|
|
8
|
+
import { EventEmitter } from 'events';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Watch event types
|
|
12
|
+
*/
|
|
13
|
+
export type WatchEventType =
|
|
14
|
+
| 'add'
|
|
15
|
+
| 'change'
|
|
16
|
+
| 'unlink'
|
|
17
|
+
| 'addDir'
|
|
18
|
+
| 'unlinkDir'
|
|
19
|
+
| 'error'
|
|
20
|
+
| 'ready';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Watch event
|
|
24
|
+
*/
|
|
25
|
+
export interface WatchEvent {
|
|
26
|
+
type: WatchEventType;
|
|
27
|
+
path: string;
|
|
28
|
+
stats?: {
|
|
29
|
+
size: number;
|
|
30
|
+
mtime: Date;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Watch options
|
|
36
|
+
*/
|
|
37
|
+
export interface WatchOptions {
|
|
38
|
+
/** Glob patterns to ignore */
|
|
39
|
+
ignored?: string | string[];
|
|
40
|
+
/** Use polling instead of native events */
|
|
41
|
+
usePolling?: boolean;
|
|
42
|
+
/** Polling interval in ms */
|
|
43
|
+
interval?: number;
|
|
44
|
+
/** Emit events for initial files */
|
|
45
|
+
emitInitial?: boolean;
|
|
46
|
+
/** Follow symlinks */
|
|
47
|
+
followSymlinks?: boolean;
|
|
48
|
+
/** Watch depth */
|
|
49
|
+
depth?: number;
|
|
50
|
+
/** Persistent watch */
|
|
51
|
+
persistent?: boolean;
|
|
52
|
+
/** Debounce delay in ms */
|
|
53
|
+
debounce?: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Watch callback
|
|
58
|
+
*/
|
|
59
|
+
export type WatchCallback = (event: WatchEvent) => void | Promise<void>;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* File watcher class
|
|
63
|
+
*/
|
|
64
|
+
export class FileWatcher extends EventEmitter {
|
|
65
|
+
private watcher: FSWatcher | null = null;
|
|
66
|
+
private paths: string[];
|
|
67
|
+
private options: WatchOptions;
|
|
68
|
+
private debounceTimers: Map<string, NodeJS.Timeout> = new Map();
|
|
69
|
+
|
|
70
|
+
constructor(paths: string | string[], options: WatchOptions = {}) {
|
|
71
|
+
super();
|
|
72
|
+
this.paths = Array.isArray(paths) ? paths : [paths];
|
|
73
|
+
this.options = {
|
|
74
|
+
ignored: options.ignored || ['**/node_modules/**', '**/.git/**'],
|
|
75
|
+
usePolling: options.usePolling || false,
|
|
76
|
+
interval: options.interval || 100,
|
|
77
|
+
emitInitial: options.emitInitial ?? true,
|
|
78
|
+
followSymlinks: options.followSymlinks ?? true,
|
|
79
|
+
depth: options.depth,
|
|
80
|
+
persistent: options.persistent ?? true,
|
|
81
|
+
debounce: options.debounce || 0,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Start watching
|
|
87
|
+
*/
|
|
88
|
+
start(): this {
|
|
89
|
+
if (this.watcher) {
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const chokidarOpts: ChokidarOptions = {
|
|
94
|
+
ignored: this.options.ignored,
|
|
95
|
+
persistent: this.options.persistent,
|
|
96
|
+
followSymlinks: this.options.followSymlinks,
|
|
97
|
+
depth: this.options.depth,
|
|
98
|
+
usePolling: this.options.usePolling,
|
|
99
|
+
interval: this.options.interval,
|
|
100
|
+
ignoreInitial: !this.options.emitInitial,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
this.watcher = chokidar.watch(this.paths, chokidarOpts);
|
|
104
|
+
|
|
105
|
+
this.watcher.on('add', (path, stats) => this.handleEvent('add', path, stats));
|
|
106
|
+
this.watcher.on('change', (path, stats) => this.handleEvent('change', path, stats));
|
|
107
|
+
this.watcher.on('unlink', (path) => this.handleEvent('unlink', path));
|
|
108
|
+
this.watcher.on('addDir', (path, stats) => this.handleEvent('addDir', path, stats));
|
|
109
|
+
this.watcher.on('unlinkDir', (path) => this.handleEvent('unlinkDir', path));
|
|
110
|
+
this.watcher.on('error', (error) => this.emit('error', error));
|
|
111
|
+
this.watcher.on('ready', () => this.emit('ready'));
|
|
112
|
+
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Stop watching
|
|
118
|
+
*/
|
|
119
|
+
async stop(): Promise<void> {
|
|
120
|
+
if (this.watcher) {
|
|
121
|
+
await this.watcher.close();
|
|
122
|
+
this.watcher = null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const timer of this.debounceTimers.values()) {
|
|
126
|
+
clearTimeout(timer);
|
|
127
|
+
}
|
|
128
|
+
this.debounceTimers.clear();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Add paths to watch
|
|
133
|
+
*/
|
|
134
|
+
add(paths: string | string[]): this {
|
|
135
|
+
const pathsArray = Array.isArray(paths) ? paths : [paths];
|
|
136
|
+
this.paths.push(...pathsArray);
|
|
137
|
+
this.watcher?.add(pathsArray);
|
|
138
|
+
return this;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Remove paths from watch
|
|
143
|
+
*/
|
|
144
|
+
unwatch(paths: string | string[]): this {
|
|
145
|
+
const pathsArray = Array.isArray(paths) ? paths : [paths];
|
|
146
|
+
this.paths = this.paths.filter((p) => !pathsArray.includes(p));
|
|
147
|
+
this.watcher?.unwatch(pathsArray);
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get watched paths
|
|
153
|
+
*/
|
|
154
|
+
getWatched(): Record<string, string[]> {
|
|
155
|
+
return this.watcher?.getWatched() || {};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Check if watcher is ready
|
|
160
|
+
*/
|
|
161
|
+
get isReady(): boolean {
|
|
162
|
+
return this.watcher !== null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Register event handler
|
|
167
|
+
*/
|
|
168
|
+
on(
|
|
169
|
+
event: 'add' | 'change' | 'unlink' | 'addDir' | 'unlinkDir' | 'all',
|
|
170
|
+
listener: (event: WatchEvent) => void
|
|
171
|
+
): this;
|
|
172
|
+
on(event: 'error', listener: (error: Error) => void): this;
|
|
173
|
+
on(event: 'ready', listener: () => void): this;
|
|
174
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
175
|
+
on(event: string, listener: (...args: any[]) => void): this {
|
|
176
|
+
return super.on(event, listener);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private handleEvent(
|
|
180
|
+
type: WatchEventType,
|
|
181
|
+
path: string,
|
|
182
|
+
stats?: { size: number; mtime: Date }
|
|
183
|
+
): void {
|
|
184
|
+
const event: WatchEvent = {
|
|
185
|
+
type,
|
|
186
|
+
path,
|
|
187
|
+
stats: stats
|
|
188
|
+
? {
|
|
189
|
+
size: stats.size,
|
|
190
|
+
mtime: stats.mtime,
|
|
191
|
+
}
|
|
192
|
+
: undefined,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
if (this.options.debounce && this.options.debounce > 0) {
|
|
196
|
+
// Debounce the event
|
|
197
|
+
if (this.debounceTimers.has(path)) {
|
|
198
|
+
clearTimeout(this.debounceTimers.get(path));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
this.debounceTimers.set(
|
|
202
|
+
path,
|
|
203
|
+
setTimeout(() => {
|
|
204
|
+
this.debounceTimers.delete(path);
|
|
205
|
+
this.emitEvent(event);
|
|
206
|
+
}, this.options.debounce)
|
|
207
|
+
);
|
|
208
|
+
} else {
|
|
209
|
+
this.emitEvent(event);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private emitEvent(event: WatchEvent): void {
|
|
214
|
+
this.emit(event.type, event);
|
|
215
|
+
this.emit('all', event);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Watch files/directories
|
|
221
|
+
*/
|
|
222
|
+
export function watch(paths: string | string[], options?: WatchOptions): FileWatcher {
|
|
223
|
+
return new FileWatcher(paths, options).start();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Watch and run callback on changes
|
|
228
|
+
*/
|
|
229
|
+
export function watchCallback(
|
|
230
|
+
paths: string | string[],
|
|
231
|
+
callback: WatchCallback,
|
|
232
|
+
options?: WatchOptions
|
|
233
|
+
): FileWatcher {
|
|
234
|
+
const watcher = watch(paths, options);
|
|
235
|
+
|
|
236
|
+
watcher.on('all', async (event) => {
|
|
237
|
+
try {
|
|
238
|
+
await callback(event);
|
|
239
|
+
} catch (error) {
|
|
240
|
+
watcher.emit('error', error);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
return watcher;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Watch for specific file types
|
|
249
|
+
*/
|
|
250
|
+
export function watchFileTypes(
|
|
251
|
+
dir: string,
|
|
252
|
+
extensions: string[],
|
|
253
|
+
callback: WatchCallback,
|
|
254
|
+
options?: WatchOptions
|
|
255
|
+
): FileWatcher {
|
|
256
|
+
const patterns = extensions.map((ext) => {
|
|
257
|
+
const e = ext.startsWith('.') ? ext : `.${ext}`;
|
|
258
|
+
return `${dir}/**/*${e}`;
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
return watchCallback(patterns, callback, options);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Watch a single file
|
|
266
|
+
*/
|
|
267
|
+
export function watchFile(
|
|
268
|
+
path: string,
|
|
269
|
+
callback: WatchCallback,
|
|
270
|
+
options?: WatchOptions
|
|
271
|
+
): FileWatcher {
|
|
272
|
+
return watchCallback(path, callback, { ...options, depth: 0 });
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Watch and debounce - only emit after changes have stopped for a duration
|
|
277
|
+
*/
|
|
278
|
+
export function watchDebounced(
|
|
279
|
+
paths: string | string[],
|
|
280
|
+
callback: WatchCallback,
|
|
281
|
+
debounceMs = 300,
|
|
282
|
+
options?: WatchOptions
|
|
283
|
+
): FileWatcher {
|
|
284
|
+
return watchCallback(paths, callback, { ...options, debounce: debounceMs });
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* One-shot watch - stop after first event
|
|
289
|
+
*/
|
|
290
|
+
export async function watchOnce(
|
|
291
|
+
paths: string | string[],
|
|
292
|
+
options?: WatchOptions
|
|
293
|
+
): Promise<WatchEvent> {
|
|
294
|
+
return new Promise((resolve, reject) => {
|
|
295
|
+
const watcher = watch(paths, { ...options, emitInitial: false });
|
|
296
|
+
|
|
297
|
+
const cleanup = async () => {
|
|
298
|
+
await watcher.stop();
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
watcher.on('all', async (event) => {
|
|
302
|
+
await cleanup();
|
|
303
|
+
resolve(event);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
watcher.on('error', async (error) => {
|
|
307
|
+
await cleanup();
|
|
308
|
+
reject(error);
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Watch with batching - collect events and emit in batches
|
|
315
|
+
*/
|
|
316
|
+
export function watchBatched(
|
|
317
|
+
paths: string | string[],
|
|
318
|
+
callback: (events: WatchEvent[]) => void | Promise<void>,
|
|
319
|
+
batchMs = 100,
|
|
320
|
+
options?: WatchOptions
|
|
321
|
+
): FileWatcher {
|
|
322
|
+
const watcher = watch(paths, options);
|
|
323
|
+
let batch: WatchEvent[] = [];
|
|
324
|
+
let timer: NodeJS.Timeout | null = null;
|
|
325
|
+
|
|
326
|
+
const flush = async () => {
|
|
327
|
+
if (batch.length > 0) {
|
|
328
|
+
const events = [...batch];
|
|
329
|
+
batch = [];
|
|
330
|
+
try {
|
|
331
|
+
await callback(events);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
watcher.emit('error', error);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
watcher.on('all', (event) => {
|
|
339
|
+
batch.push(event);
|
|
340
|
+
|
|
341
|
+
if (timer) {
|
|
342
|
+
clearTimeout(timer);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
timer = setTimeout(flush, batchMs);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// Extend stop to flush remaining
|
|
349
|
+
const originalStop = watcher.stop.bind(watcher);
|
|
350
|
+
watcher.stop = async () => {
|
|
351
|
+
if (timer) {
|
|
352
|
+
clearTimeout(timer);
|
|
353
|
+
}
|
|
354
|
+
await flush();
|
|
355
|
+
await originalStop();
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
return watcher;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Watch with filtering
|
|
363
|
+
*/
|
|
364
|
+
export function watchFiltered(
|
|
365
|
+
paths: string | string[],
|
|
366
|
+
filter: (event: WatchEvent) => boolean,
|
|
367
|
+
callback: WatchCallback,
|
|
368
|
+
options?: WatchOptions
|
|
369
|
+
): FileWatcher {
|
|
370
|
+
return watchCallback(
|
|
371
|
+
paths,
|
|
372
|
+
async (event) => {
|
|
373
|
+
if (filter(event)) {
|
|
374
|
+
await callback(event);
|
|
375
|
+
}
|
|
376
|
+
},
|
|
377
|
+
options
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Watch only for specific event types
|
|
383
|
+
*/
|
|
384
|
+
export function watchEvents(
|
|
385
|
+
paths: string | string[],
|
|
386
|
+
eventTypes: WatchEventType[],
|
|
387
|
+
callback: WatchCallback,
|
|
388
|
+
options?: WatchOptions
|
|
389
|
+
): FileWatcher {
|
|
390
|
+
return watchFiltered(paths, (event) => eventTypes.includes(event.type), callback, options);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Watch only for file changes (add, change, unlink)
|
|
395
|
+
*/
|
|
396
|
+
export function watchFiles(
|
|
397
|
+
paths: string | string[],
|
|
398
|
+
callback: WatchCallback,
|
|
399
|
+
options?: WatchOptions
|
|
400
|
+
): FileWatcher {
|
|
401
|
+
return watchEvents(paths, ['add', 'change', 'unlink'], callback, options);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Watch only for directory changes
|
|
406
|
+
*/
|
|
407
|
+
export function watchDirs(
|
|
408
|
+
paths: string | string[],
|
|
409
|
+
callback: WatchCallback,
|
|
410
|
+
options?: WatchOptions
|
|
411
|
+
): FileWatcher {
|
|
412
|
+
return watchEvents(paths, ['addDir', 'unlinkDir'], callback, options);
|
|
413
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -341,7 +341,7 @@ export function unreachable(message?: string): never {
|
|
|
341
341
|
* Todo marker (throws at runtime)
|
|
342
342
|
*/
|
|
343
343
|
export function todo(message?: string): never {
|
|
344
|
-
throw new Error(`
|
|
344
|
+
throw new Error(`Not implemented: ${message ?? 'No details'}`);
|
|
345
345
|
}
|
|
346
346
|
|
|
347
347
|
/**
|
|
@@ -472,3 +472,5 @@ export function noop(): void {}
|
|
|
472
472
|
export function constant<T>(value: T): () => T {
|
|
473
473
|
return () => value;
|
|
474
474
|
}
|
|
475
|
+
|
|
476
|
+
|
package/src/math.ts
CHANGED
|
@@ -124,7 +124,7 @@ export const vec3Math = {
|
|
|
124
124
|
left: (): Vec3 => vec3(-1, 0, 0),
|
|
125
125
|
right: (): Vec3 => vec3(1, 0, 0),
|
|
126
126
|
|
|
127
|
-
add: (a: Vec3, b: Vec3): Vec3 => (
|
|
127
|
+
add: (a: Vec3, b: Vec3): Vec3 => vec3(a.x + b.x, a.y + b.y, a.z + b.z),
|
|
128
128
|
sub: (a: Vec3, b: Vec3): Vec3 => ({ x: a.x - b.x, y: a.y - b.y, z: a.z - b.z }),
|
|
129
129
|
mul: (a: Vec3, s: number): Vec3 => ({ x: a.x * s, y: a.y * s, z: a.z * s }),
|
|
130
130
|
div: (a: Vec3, s: number): Vec3 => ({ x: a.x / s, y: a.y / s, z: a.z / s }),
|
package/src/string.basic.test.ts
CHANGED
package/src/string.test.ts
CHANGED
|
@@ -54,7 +54,7 @@ import {
|
|
|
54
54
|
wordWrap,
|
|
55
55
|
levenshtein,
|
|
56
56
|
similarity,
|
|
57
|
-
extractTraits
|
|
57
|
+
extractTraits,
|
|
58
58
|
} from './string.js';
|
|
59
59
|
|
|
60
60
|
describe('@holoscript/std string utilities', () => {
|
|
@@ -187,7 +187,7 @@ describe('@holoscript/std string utilities', () => {
|
|
|
187
187
|
expect(isBlank(' ')).toBe(true);
|
|
188
188
|
expect(isBlank('\t\n')).toBe(true);
|
|
189
189
|
expect(isBlank('test')).toBe(false);
|
|
190
|
-
|
|
190
|
+
|
|
191
191
|
expect(isNotBlank('')).toBe(false);
|
|
192
192
|
expect(isNotBlank(' ')).toBe(false);
|
|
193
193
|
expect(isNotBlank('test')).toBe(true);
|
|
@@ -287,7 +287,7 @@ describe('@holoscript/std string utilities', () => {
|
|
|
287
287
|
expect(str1.length).toBe(10);
|
|
288
288
|
expect(str2.length).toBe(10);
|
|
289
289
|
expect(str1).not.toBe(str2); // Should be different
|
|
290
|
-
|
|
290
|
+
|
|
291
291
|
const customStr = randomString(5, 'ABC');
|
|
292
292
|
expect(customStr.length).toBe(5);
|
|
293
293
|
expect(customStr).toMatch(/^[ABC]+$/);
|
|
@@ -332,4 +332,4 @@ describe('@holoscript/std string utilities', () => {
|
|
|
332
332
|
expect(traits).toEqual(['Entity', 'Cube']);
|
|
333
333
|
});
|
|
334
334
|
});
|
|
335
|
-
});
|
|
335
|
+
});
|
package/src/string.ts
CHANGED
|
@@ -116,8 +116,8 @@ export function truncate(s: string, maxLength: number, ellipsis = '...'): string
|
|
|
116
116
|
export function truncateMiddle(s: string, maxLength: number, ellipsis = '...'): string {
|
|
117
117
|
if (s.length <= maxLength) return s;
|
|
118
118
|
const availableLength = maxLength - ellipsis.length;
|
|
119
|
-
const leftLength = Math.
|
|
120
|
-
const rightLength = Math.
|
|
119
|
+
const leftLength = Math.floor(availableLength / 2);
|
|
120
|
+
const rightLength = Math.ceil(availableLength / 2);
|
|
121
121
|
return s.slice(0, leftLength) + ellipsis + s.slice(-rightLength);
|
|
122
122
|
}
|
|
123
123
|
|
|
@@ -144,7 +144,7 @@ export function count(s: string, substring: string): number {
|
|
|
144
144
|
let pos = 0;
|
|
145
145
|
while ((pos = s.indexOf(substring, pos)) !== -1) {
|
|
146
146
|
count++;
|
|
147
|
-
pos +=
|
|
147
|
+
pos += 1;
|
|
148
148
|
}
|
|
149
149
|
return count;
|
|
150
150
|
}
|
|
@@ -281,6 +281,7 @@ export function numberWithCommas(n: number): string {
|
|
|
281
281
|
export function formatBytes(bytes: number, decimals = 2): string {
|
|
282
282
|
if (bytes === 0) return '0 B';
|
|
283
283
|
const k = 1024;
|
|
284
|
+
if (bytes < 0) return `${bytes} B`;
|
|
284
285
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
|
285
286
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
286
287
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i];
|
|
@@ -293,13 +294,11 @@ export function formatDuration(ms: number): string {
|
|
|
293
294
|
if (ms < 1000) return `${ms}ms`;
|
|
294
295
|
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
295
296
|
if (ms < 3600000) {
|
|
296
|
-
|
|
297
|
-
const secs = Math.floor((ms % 60000) / 1000);
|
|
298
|
-
return `${mins}m ${secs}s`;
|
|
297
|
+
return `${(ms / 60000).toFixed(1)}m`;
|
|
299
298
|
}
|
|
300
|
-
const
|
|
301
|
-
const
|
|
302
|
-
return `${
|
|
299
|
+
const _hours = Math.floor(ms / 3600000);
|
|
300
|
+
const _mins = Math.floor((ms % 3600000) / 60000);
|
|
301
|
+
return `${(ms / 3600000).toFixed(1)}h`;
|
|
303
302
|
}
|
|
304
303
|
|
|
305
304
|
/**
|
|
@@ -495,22 +494,38 @@ export function levenshtein(a: string, b: string): number {
|
|
|
495
494
|
return matrix[b.length][a.length];
|
|
496
495
|
}
|
|
497
496
|
|
|
497
|
+
function lcsLength(a: string, b: string): number {
|
|
498
|
+
const n = b.length;
|
|
499
|
+
const dp: number[] = new Array(n + 1).fill(0);
|
|
500
|
+
for (let i = 0; i < a.length; i++) {
|
|
501
|
+
let prev = 0;
|
|
502
|
+
for (let j = 0; j < n; j++) {
|
|
503
|
+
const tmp = dp[j + 1];
|
|
504
|
+
dp[j + 1] = a[i] === b[j] ? prev + 1 : Math.max(dp[j + 1], dp[j]);
|
|
505
|
+
prev = tmp;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return dp[n];
|
|
509
|
+
}
|
|
510
|
+
|
|
498
511
|
/**
|
|
499
|
-
* Check similarity between two strings (0-1)
|
|
512
|
+
* Check similarity between two strings (0-1) using Sorensen-Dice over LCS
|
|
500
513
|
*/
|
|
501
514
|
export function similarity(a: string, b: string): number {
|
|
502
|
-
const
|
|
503
|
-
if (
|
|
504
|
-
return
|
|
515
|
+
const total = a.length + b.length;
|
|
516
|
+
if (total === 0) return 1;
|
|
517
|
+
return (2 * lcsLength(a, b)) / total;
|
|
505
518
|
}
|
|
506
519
|
|
|
507
520
|
/**
|
|
508
|
-
* Extract HoloScript
|
|
521
|
+
* Extract HoloScript trait annotations (@name) and JSX component names from code
|
|
509
522
|
*/
|
|
510
523
|
export function extractTraits(code: string): string[] {
|
|
511
|
-
const re = /@([a-zA-Z_]\w*)/g;
|
|
512
524
|
const traits = new Set<string>();
|
|
513
|
-
let m;
|
|
514
|
-
|
|
525
|
+
let m: RegExpExecArray | null;
|
|
526
|
+
const atRe = /@([a-zA-Z_]\w*)/g;
|
|
527
|
+
while ((m = atRe.exec(code)) !== null) traits.add(`@${m[1]}`);
|
|
528
|
+
const tagRe = /<([A-Z][a-zA-Z0-9]*)\b/g;
|
|
529
|
+
while ((m = tagRe.exec(code)) !== null) traits.add(m[1]);
|
|
515
530
|
return [...traits];
|
|
516
531
|
}
|
package/src/time.ts
CHANGED
|
@@ -151,7 +151,7 @@ export async function timed<T>(label: string, fn: () => T | Promise<T>): Promise
|
|
|
151
151
|
return await fn();
|
|
152
152
|
} finally {
|
|
153
153
|
const duration = hrTime() - start;
|
|
154
|
-
|
|
154
|
+
void duration; // timing available for callers via return value
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
|
package/src/traits/ARTraits.ts
CHANGED
|
@@ -39,7 +39,7 @@ export const ARTraits: TraitDefinition[] = [
|
|
|
39
39
|
'The expected physical size of the image/marker in meters (for image/qr types)',
|
|
40
40
|
},
|
|
41
41
|
],
|
|
42
|
-
validation: (
|
|
42
|
+
validation: (_node: any) => {
|
|
43
43
|
return { valid: true }; // Allowed on any Node (Zones, Objects, UI)
|
|
44
44
|
},
|
|
45
45
|
},
|
|
@@ -94,7 +94,7 @@ export const ARTraits: TraitDefinition[] = [
|
|
|
94
94
|
description: 'Which layer the user shifts into.',
|
|
95
95
|
},
|
|
96
96
|
],
|
|
97
|
-
validation: (
|
|
97
|
+
validation: (_node: any) => {
|
|
98
98
|
return { valid: true };
|
|
99
99
|
},
|
|
100
100
|
},
|
package/src/traits/IoTTraits.ts
CHANGED
|
@@ -40,7 +40,7 @@ export const IoTTraits: TraitDefinition[] = [
|
|
|
40
40
|
description: 'The target refresh rate in Hertz.',
|
|
41
41
|
},
|
|
42
42
|
],
|
|
43
|
-
validation: (
|
|
43
|
+
validation: (_node: any) => {
|
|
44
44
|
return { valid: true };
|
|
45
45
|
},
|
|
46
46
|
},
|
|
@@ -93,7 +93,7 @@ export const IoTTraits: TraitDefinition[] = [
|
|
|
93
93
|
description: 'What the digital twin should do when physical telemetry fails.',
|
|
94
94
|
},
|
|
95
95
|
],
|
|
96
|
-
validation: (
|
|
96
|
+
validation: (_node: any) => {
|
|
97
97
|
return { valid: true };
|
|
98
98
|
},
|
|
99
99
|
},
|
package/src/traits/VRRTraits.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @fileoverview VRR (Virtual Reality Reality) Trait Definitions
|
|
3
3
|
* @module @holoscript/std/traits
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* HIGH - Define VRR Trait Library for 1:1 Digital Twins
|
|
6
6
|
*
|
|
7
7
|
* PURPOSE:
|
|
8
8
|
* Define standard HoloScript traits for VRR (Virtual Reality Reality) features:
|
|
@@ -103,7 +103,7 @@
|
|
|
103
103
|
* - BusinessQuestTools.ts (quest builder uses traits)
|
|
104
104
|
*
|
|
105
105
|
* RESEARCH REFERENCES:
|
|
106
|
-
* -
|
|
106
|
+
* - HOLOLAND_INTEGRATION_TASKS.md (VRRTraits section)
|
|
107
107
|
* - VRRCompiler.ts (trait requirements)
|
|
108
108
|
* - uAA2++_Protocol/5.GROW P.029: "Machine Customers for VR Platforms"
|
|
109
109
|
*
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import * as ecPrims from '../EconomicPrimitives.js';
|
|
3
|
+
|
|
4
|
+
describe('EconomicPrimitives', () => {
|
|
5
|
+
it('should have calculating helpers', () => {
|
|
6
|
+
expect(ecPrims.calculateDepreciation).toBeDefined();
|
|
7
|
+
expect(ecPrims.calculateDepreciation(1.0, 0.0001, 0)).toBe(1.0);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should export bonding curve formulas', () => {
|
|
11
|
+
expect(ecPrims.bondingCurvePrice).toBeDefined();
|
|
12
|
+
expect(ecPrims.bondingCurvePrice(10, 1.0, 2.0, 'linear')).toBe(10);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { EconomicTraits, getEconomicTraitNames } from '../EconomicTraits.js';
|
|
3
|
+
|
|
4
|
+
describe('EconomicTraits', () => {
|
|
5
|
+
it('should export standard economic traits', () => {
|
|
6
|
+
expect(EconomicTraits).toBeDefined();
|
|
7
|
+
expect(EconomicTraits.tradeable).toBeDefined();
|
|
8
|
+
expect(EconomicTraits.depreciating).toBeDefined();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should allow getting trait names', () => {
|
|
12
|
+
const names = getEconomicTraitNames();
|
|
13
|
+
expect(names).toContain('@tradeable');
|
|
14
|
+
expect(names.length).toBeGreaterThan(0);
|
|
15
|
+
});
|
|
16
|
+
});
|