@holoscript/std 6.0.3 → 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/dist/{chunk-7HVUYGPS.js → chunk-DZHAVOCH.js} +12 -5
- package/dist/chunk-DZHAVOCH.js.map +1 -0
- package/dist/chunk-PR4QN5HX.js +43 -0
- package/dist/chunk-PR4QN5HX.js.map +1 -0
- package/dist/chunk-XBA4ARST.js +9313 -0
- package/dist/chunk-XBA4ARST.js.map +1 -0
- package/dist/{chunk-W2Q3LUCM.js → chunk-XJIFG7G3.js} +3 -3
- package/dist/chunk-XJIFG7G3.js.map +1 -0
- package/dist/collections.js +1 -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 +12660 -3435
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/dist/math.cjs +6 -2
- package/dist/math.cjs.map +1 -1
- package/dist/math.js +2 -1
- package/dist/string.cjs +2 -2
- package/dist/string.cjs.map +1 -1
- package/dist/string.js +2 -1
- package/dist/time.js +1 -0
- package/dist/traits/EconomicPrimitives.js +1 -0
- package/dist/traits/EconomicTraits.js +1 -0
- package/dist/traits/VRRTraits.d.ts +2 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -2
- package/src/__tests__/index.test.ts +8 -8
- package/src/__tests__/standard-library-fundamentals.test.ts +21 -4
- package/src/__tests__/string-decoupled.test.ts +6 -7
- 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.ts +2 -2
- 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 +3 -9
- package/src/traits/__tests__/EconomicPrimitives.test.ts +7 -7
- package/src/traits/__tests__/EconomicTraits.test.ts +9 -7
- package/src/types.ts +10 -3
- package/tsup.config.ts +3 -0
- package/CHANGELOG.md +0 -7
- package/dist/chunk-7HVUYGPS.js.map +0 -1
- package/dist/chunk-W2Q3LUCM.js.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.ts
CHANGED
|
@@ -296,8 +296,8 @@ export function formatDuration(ms: number): string {
|
|
|
296
296
|
if (ms < 3600000) {
|
|
297
297
|
return `${(ms / 60000).toFixed(1)}m`;
|
|
298
298
|
}
|
|
299
|
-
const
|
|
300
|
-
const
|
|
299
|
+
const _hours = Math.floor(ms / 3600000);
|
|
300
|
+
const _mins = Math.floor((ms % 3600000) / 60000);
|
|
301
301
|
return `${(ms / 3600000).toFixed(1)}h`;
|
|
302
302
|
}
|
|
303
303
|
|
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
|
*
|
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
|
|
3
|
-
// Auto-generated test for ARTraits
|
|
4
|
-
// Source: packages/std/src/traits/ARTraits.ts
|
|
2
|
+
import * as ARTraits from '../ARTraits.js';
|
|
5
3
|
|
|
6
4
|
describe('ARTraits', () => {
|
|
7
|
-
it('should
|
|
8
|
-
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('should have basic functionality', () => {
|
|
12
|
-
// TODO: Add meaningful tests for ARTraits
|
|
5
|
+
it('should export AR primitives if any', () => {
|
|
6
|
+
expect(ARTraits).toBeDefined();
|
|
13
7
|
});
|
|
14
8
|
});
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
|
|
3
|
-
// Auto-generated test for EconomicPrimitives
|
|
4
|
-
// Source: packages/std/src/traits/EconomicPrimitives.ts
|
|
2
|
+
import * as ecPrims from '../EconomicPrimitives.js';
|
|
5
3
|
|
|
6
4
|
describe('EconomicPrimitives', () => {
|
|
7
|
-
it('should
|
|
8
|
-
|
|
5
|
+
it('should have calculating helpers', () => {
|
|
6
|
+
expect(ecPrims.calculateDepreciation).toBeDefined();
|
|
7
|
+
expect(ecPrims.calculateDepreciation(1.0, 0.0001, 0)).toBe(1.0);
|
|
9
8
|
});
|
|
10
9
|
|
|
11
|
-
it('should
|
|
12
|
-
|
|
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
13
|
});
|
|
14
14
|
});
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
|
|
3
|
-
// Auto-generated test for EconomicTraits
|
|
4
|
-
// Source: packages/std/src/traits/EconomicTraits.ts
|
|
2
|
+
import { EconomicTraits, getEconomicTraitNames } from '../EconomicTraits.js';
|
|
5
3
|
|
|
6
4
|
describe('EconomicTraits', () => {
|
|
7
|
-
it('should
|
|
8
|
-
|
|
5
|
+
it('should export standard economic traits', () => {
|
|
6
|
+
expect(EconomicTraits).toBeDefined();
|
|
7
|
+
expect(EconomicTraits.tradeable).toBeDefined();
|
|
8
|
+
expect(EconomicTraits.depreciating).toBeDefined();
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
-
it('should
|
|
12
|
-
|
|
11
|
+
it('should allow getting trait names', () => {
|
|
12
|
+
const names = getEconomicTraitNames();
|
|
13
|
+
expect(names).toContain('@tradeable');
|
|
14
|
+
expect(names.length).toBeGreaterThan(0);
|
|
13
15
|
});
|
|
14
16
|
});
|
package/src/types.ts
CHANGED
|
@@ -183,14 +183,21 @@ export type DeepPartial<T> = {
|
|
|
183
183
|
* Create a Vec2
|
|
184
184
|
*/
|
|
185
185
|
export function vec2(x: number = 0, y: number = 0): Vec2 {
|
|
186
|
-
|
|
186
|
+
const v = [x, y] as unknown as Vec2;
|
|
187
|
+
Object.defineProperty(v, 'x', { value: x, writable: true, enumerable: false, configurable: true });
|
|
188
|
+
Object.defineProperty(v, 'y', { value: y, writable: true, enumerable: false, configurable: true });
|
|
189
|
+
return v;
|
|
187
190
|
}
|
|
188
191
|
|
|
189
192
|
/**
|
|
190
193
|
* Create a Vec3
|
|
191
194
|
*/
|
|
192
195
|
export function vec3(x: number = 0, y: number = 0, z: number = 0): Vec3 {
|
|
193
|
-
|
|
196
|
+
const v = [x, y, z] as unknown as Vec3;
|
|
197
|
+
Object.defineProperty(v, 'x', { value: x, writable: true, enumerable: false, configurable: true });
|
|
198
|
+
Object.defineProperty(v, 'y', { value: y, writable: true, enumerable: false, configurable: true });
|
|
199
|
+
Object.defineProperty(v, 'z', { value: z, writable: true, enumerable: false, configurable: true });
|
|
200
|
+
return v;
|
|
194
201
|
}
|
|
195
202
|
|
|
196
203
|
/**
|
|
@@ -268,7 +275,7 @@ export function vec3ToArray(v: Vec3): Vec3Array {
|
|
|
268
275
|
* Convert array to Vec3
|
|
269
276
|
*/
|
|
270
277
|
export function arrayToVec3(arr: Vec3Array | number[]): Vec3 {
|
|
271
|
-
return
|
|
278
|
+
return vec3(arr[0] || 0, arr[1] || 0, arr[2] || 0);
|
|
272
279
|
}
|
|
273
280
|
|
|
274
281
|
/**
|
package/tsup.config.ts
CHANGED
|
@@ -9,9 +9,12 @@ export default defineConfig({
|
|
|
9
9
|
'src/time.ts',
|
|
10
10
|
'src/traits/EconomicPrimitives.ts',
|
|
11
11
|
'src/traits/EconomicTraits.ts',
|
|
12
|
+
// Merged from @holoscript/fs (2026-04-29) — exposed via the ./fs subpath.
|
|
13
|
+
'src/fs/index.ts',
|
|
12
14
|
],
|
|
13
15
|
format: ['esm', 'cjs'],
|
|
14
16
|
dts: false,
|
|
15
17
|
clean: true,
|
|
16
18
|
sourcemap: true,
|
|
19
|
+
external: ['chokidar', 'glob'],
|
|
17
20
|
});
|