@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.
Files changed (142) hide show
  1. package/LICENSE +21 -0
  2. package/dist/__tests__/EconomicPrimitives.test.d.ts +2 -0
  3. package/dist/__tests__/EconomicTraits.test.d.ts +2 -0
  4. package/dist/__tests__/SimulationLabPrimitives.test.d.ts +2 -0
  5. package/dist/__tests__/collections.test.d.ts +2 -0
  6. package/dist/__tests__/events.test.d.ts +2 -0
  7. package/dist/__tests__/index.test.d.ts +2 -0
  8. package/dist/__tests__/index.test.d.ts.map +1 -0
  9. package/dist/__tests__/materials.test.d.ts +2 -0
  10. package/dist/__tests__/math.test.d.ts +2 -0
  11. package/dist/__tests__/physics.test.d.ts +2 -0
  12. package/dist/__tests__/spatial.test.d.ts +2 -0
  13. package/dist/__tests__/standard-library-fundamentals.test.d.ts +2 -0
  14. package/dist/__tests__/standard-library-fundamentals.test.d.ts.map +1 -0
  15. package/dist/__tests__/string-and-time-utilities.test.d.ts +2 -0
  16. package/dist/__tests__/string-and-time-utilities.test.d.ts.map +1 -0
  17. package/dist/__tests__/string-decoupled.test.d.ts +2 -0
  18. package/dist/__tests__/string.test.d.ts +2 -0
  19. package/dist/__tests__/time.test.d.ts +2 -0
  20. package/dist/chunk-DZHAVOCH.js +686 -0
  21. package/dist/chunk-DZHAVOCH.js.map +1 -0
  22. package/dist/chunk-P4O3WNG2.js +287 -0
  23. package/dist/chunk-P4O3WNG2.js.map +1 -0
  24. package/dist/chunk-PFX2JM4X.js +683 -0
  25. package/dist/chunk-PFX2JM4X.js.map +1 -0
  26. package/dist/chunk-PR4QN5HX.js +43 -0
  27. package/dist/chunk-PR4QN5HX.js.map +1 -0
  28. package/dist/chunk-WS6W35YC.js +443 -0
  29. package/dist/chunk-WS6W35YC.js.map +1 -0
  30. package/dist/chunk-XBA4ARST.js +9313 -0
  31. package/dist/chunk-XBA4ARST.js.map +1 -0
  32. package/dist/chunk-XJIFG7G3.js +348 -0
  33. package/dist/chunk-XJIFG7G3.js.map +1 -0
  34. package/dist/chunk-ZXVZAQCZ.js +470 -0
  35. package/dist/chunk-ZXVZAQCZ.js.map +1 -0
  36. package/dist/collections.cjs +715 -0
  37. package/dist/collections.cjs.map +1 -0
  38. package/dist/collections.d.ts +177 -0
  39. package/dist/collections.js +18 -0
  40. package/dist/collections.js.map +1 -0
  41. package/dist/events.d.ts +32 -0
  42. package/dist/fs/__tests__/file-watch-system.test.d.ts +2 -0
  43. package/dist/fs/__tests__/file-watch-system.test.d.ts.map +1 -0
  44. package/dist/fs/__tests__/path-utilities.test.d.ts +2 -0
  45. package/dist/fs/__tests__/path-utilities.test.d.ts.map +1 -0
  46. package/dist/fs/__tests__/path.test.d.ts +2 -0
  47. package/dist/fs/__tests__/path.test.d.ts.map +1 -0
  48. package/dist/fs/fs.d.ts +288 -0
  49. package/dist/fs/fs.d.ts.map +1 -0
  50. package/dist/fs/index.cjs +9338 -0
  51. package/dist/fs/index.cjs.map +1 -0
  52. package/dist/fs/index.d.ts +51 -0
  53. package/dist/fs/index.d.ts.map +1 -0
  54. package/dist/fs/index.js +204 -0
  55. package/dist/fs/index.js.map +1 -0
  56. package/dist/fs/path.d.ts +135 -0
  57. package/dist/fs/path.d.ts.map +1 -0
  58. package/dist/fs/watch.d.ts +133 -0
  59. package/dist/fs/watch.d.ts.map +1 -0
  60. package/dist/index.cjs +13326 -0
  61. package/dist/index.cjs.map +1 -0
  62. package/dist/index.d.ts +102 -0
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +1275 -190
  65. package/dist/index.js.map +1 -0
  66. package/dist/materials.d.ts +50 -0
  67. package/dist/math.cjs +595 -0
  68. package/dist/math.cjs.map +1 -0
  69. package/dist/math.d.ts +162 -0
  70. package/dist/math.js +54 -0
  71. package/dist/math.js.map +1 -0
  72. package/dist/physics.d.ts +59 -0
  73. package/dist/spatial.d.ts +82 -0
  74. package/dist/string.basic.test.d.ts +5 -0
  75. package/dist/string.basic.test.d.ts.map +1 -0
  76. package/dist/string.cjs +422 -0
  77. package/dist/string.cjs.map +1 -0
  78. package/dist/string.d.ts +212 -0
  79. package/dist/string.d.ts.map +1 -1
  80. package/dist/string.js +108 -0
  81. package/dist/string.js.map +1 -0
  82. package/dist/string.test.d.ts +5 -0
  83. package/dist/string.test.d.ts.map +1 -0
  84. package/dist/time.cjs +512 -0
  85. package/dist/time.cjs.map +1 -0
  86. package/dist/time.d.ts +215 -0
  87. package/dist/time.js +44 -0
  88. package/dist/time.js.map +1 -0
  89. package/dist/traits/ARTraits.d.ts +14 -0
  90. package/dist/traits/EconomicPrimitives.cjs +335 -0
  91. package/dist/traits/EconomicPrimitives.cjs.map +1 -0
  92. package/dist/traits/EconomicPrimitives.d.ts +345 -0
  93. package/dist/traits/EconomicPrimitives.js +56 -0
  94. package/dist/traits/EconomicPrimitives.js.map +1 -0
  95. package/dist/traits/EconomicTraits.cjs +471 -0
  96. package/dist/traits/EconomicTraits.cjs.map +1 -0
  97. package/dist/traits/EconomicTraits.d.ts +71 -0
  98. package/dist/traits/EconomicTraits.js +18 -0
  99. package/dist/traits/EconomicTraits.js.map +1 -0
  100. package/dist/traits/IoTTraits.d.ts +14 -0
  101. package/dist/traits/SimulationLabPrimitives.d.ts +187 -0
  102. package/dist/traits/SimulationLabTraits.d.ts +42 -0
  103. package/dist/traits/VRRTraits.d.ts +150 -0
  104. package/dist/traits/__tests__/ARTraits.test.d.ts +2 -0
  105. package/dist/traits/__tests__/ARTraits.test.d.ts.map +1 -0
  106. package/dist/traits/__tests__/EconomicPrimitives.test.d.ts +2 -0
  107. package/dist/traits/__tests__/EconomicPrimitives.test.d.ts.map +1 -0
  108. package/dist/traits/__tests__/EconomicTraits.test.d.ts +2 -0
  109. package/dist/traits/__tests__/EconomicTraits.test.d.ts.map +1 -0
  110. package/dist/types.d.ts +202 -0
  111. package/dist/types.d.ts.map +1 -1
  112. package/error-utf8.log +6 -0
  113. package/error.log +0 -0
  114. package/package.json +35 -18
  115. package/src/__tests__/index.test.ts +14 -0
  116. package/src/__tests__/{Sprint33.test.ts → standard-library-fundamentals.test.ts} +91 -74
  117. package/src/__tests__/{Sprint56.test.ts → string-and-time-utilities.test.ts} +64 -64
  118. package/src/__tests__/string-decoupled.test.ts +6 -9
  119. package/src/fs/__tests__/file-watch-system.test.ts +675 -0
  120. package/src/fs/__tests__/path-utilities.test.ts +375 -0
  121. package/src/fs/__tests__/path.test.ts +287 -0
  122. package/src/fs/fs.ts +692 -0
  123. package/src/fs/index.ts +194 -0
  124. package/src/fs/path.ts +310 -0
  125. package/src/fs/watch.ts +413 -0
  126. package/src/index.ts +3 -1
  127. package/src/math.ts +1 -1
  128. package/src/string.basic.test.ts +1 -1
  129. package/src/string.test.ts +4 -4
  130. package/src/string.ts +32 -17
  131. package/src/time.ts +1 -1
  132. package/src/traits/ARTraits.ts +2 -2
  133. package/src/traits/IoTTraits.ts +2 -2
  134. package/src/traits/VRRTraits.ts +2 -2
  135. package/src/traits/__tests__/ARTraits.test.ts +8 -0
  136. package/src/traits/__tests__/EconomicPrimitives.test.ts +14 -0
  137. package/src/traits/__tests__/EconomicTraits.test.ts +16 -0
  138. package/src/types.ts +10 -3
  139. package/std_error.log +5 -0
  140. package/tsup.config.ts +20 -0
  141. package/dist/__tests__/Sprint33.test.d.ts.map +0 -1
  142. package/dist/__tests__/Sprint56.test.d.ts.map +0 -1
@@ -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(`TODO: ${message ?? 'Not implemented'}`);
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 => ({ x: a.x + b.x, y: a.y + b.y, z: a.z + b.z }),
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 }),
@@ -11,4 +11,4 @@ describe('formatBytes basic test', () => {
11
11
  expect(formatBytes(1024)).toBe('1 KB');
12
12
  expect(formatBytes(1048576)).toBe('1 MB');
13
13
  });
14
- });
14
+ });
@@ -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.ceil(availableLength / 2);
120
- const rightLength = Math.floor(availableLength / 2);
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 += substring.length;
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
- const mins = Math.floor(ms / 60000);
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 hours = Math.floor(ms / 3600000);
301
- const mins = Math.floor((ms % 3600000) / 60000);
302
- return `${hours}h ${mins}m`;
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 maxLen = Math.max(a.length, b.length);
503
- if (maxLen === 0) return 1;
504
- return 1 - levenshtein(a, b) / maxLen;
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 traits (e.g., @grabbable) from a code string
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
- while ((m = re.exec(code)) !== null) traits.add(`@${m[1]}`);
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
- console.log(`[${label}] ${duration.toFixed(2)}ms`);
154
+ void duration; // timing available for callers via return value
155
155
  }
156
156
  }
157
157
 
@@ -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: (node: any) => {
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: (node: any) => {
97
+ validation: (_node: any) => {
98
98
  return { valid: true };
99
99
  },
100
100
  },
@@ -40,7 +40,7 @@ export const IoTTraits: TraitDefinition[] = [
40
40
  description: 'The target refresh rate in Hertz.',
41
41
  },
42
42
  ],
43
- validation: (node: any) => {
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: (node: any) => {
96
+ validation: (_node: any) => {
97
97
  return { valid: true };
98
98
  },
99
99
  },
@@ -2,7 +2,7 @@
2
2
  * @fileoverview VRR (Virtual Reality Reality) Trait Definitions
3
3
  * @module @holoscript/std/traits
4
4
  *
5
- * TODO: HIGH - Define VRR Trait Library for 1:1 Digital Twins
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
- * - HOLOLAND_INTEGRATION_TODOS.md (VRRTraits section)
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,8 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import * as ARTraits from '../ARTraits.js';
3
+
4
+ describe('ARTraits', () => {
5
+ it('should export AR primitives if any', () => {
6
+ expect(ARTraits).toBeDefined();
7
+ });
8
+ });
@@ -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
+ });