@mikrojs/native 0.0.7

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 (109) hide show
  1. package/CMakeLists.txt +198 -0
  2. package/LICENSE +21 -0
  3. package/README.md +49 -0
  4. package/cmake/mikrojs_bytecode.cmake +146 -0
  5. package/cmake.js +22 -0
  6. package/dist/index.d.ts +52 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +132 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/types.d.ts +43 -0
  11. package/dist/types.d.ts.map +1 -0
  12. package/dist/types.js +2 -0
  13. package/dist/types.js.map +1 -0
  14. package/include/byteorder_apple.h +11 -0
  15. package/include/byteorder_windows.h +12 -0
  16. package/include/mikrojs/cbor_helpers.h +24 -0
  17. package/include/mikrojs/cutils_wrap.h +59 -0
  18. package/include/mikrojs/errors.h +144 -0
  19. package/include/mikrojs/mem.h +11 -0
  20. package/include/mikrojs/mik_color.h +32 -0
  21. package/include/mikrojs/mikrojs.h +331 -0
  22. package/include/mikrojs/platform.h +82 -0
  23. package/include/mikrojs/private.h +281 -0
  24. package/include/mikrojs/utils.h +125 -0
  25. package/package.json +100 -0
  26. package/prebuilds/darwin-arm64/mikrojs.napi.node +0 -0
  27. package/prebuilds/linux-arm64/mikrojs.napi.node +0 -0
  28. package/prebuilds/linux-x64/mikrojs.napi.node +0 -0
  29. package/runtime/ble/ble.ts +231 -0
  30. package/runtime/ble/types.ts +194 -0
  31. package/runtime/ble/uuid.ts +89 -0
  32. package/runtime/ble/validators.ts +61 -0
  33. package/runtime/cbor/cbor.ts +1 -0
  34. package/runtime/cbor/types.ts +8 -0
  35. package/runtime/console/types.ts +50 -0
  36. package/runtime/env/env.ts +17 -0
  37. package/runtime/env/types.ts +12 -0
  38. package/runtime/format/types.ts +4 -0
  39. package/runtime/fs/fs.ts +93 -0
  40. package/runtime/fs/types.ts +92 -0
  41. package/runtime/globals.d.ts +87 -0
  42. package/runtime/http/helpers.ts +222 -0
  43. package/runtime/http/native.ts +151 -0
  44. package/runtime/http/request.ts +25 -0
  45. package/runtime/i2c/i2c.ts +35 -0
  46. package/runtime/i2c/types.ts +55 -0
  47. package/runtime/inspect/types.ts +10 -0
  48. package/runtime/internal.d.ts +456 -0
  49. package/runtime/kv/nvs.ts +17 -0
  50. package/runtime/kv/rtc.ts +17 -0
  51. package/runtime/kv/shared.ts +107 -0
  52. package/runtime/kv/types.ts +150 -0
  53. package/runtime/neopixel/neopixel.ts +38 -0
  54. package/runtime/neopixel/types.ts +27 -0
  55. package/runtime/pin/pin.ts +51 -0
  56. package/runtime/pin/types.ts +49 -0
  57. package/runtime/pwm/pwm.ts +32 -0
  58. package/runtime/pwm/types.ts +29 -0
  59. package/runtime/reader/reader.ts +167 -0
  60. package/runtime/reader/types.ts +34 -0
  61. package/runtime/result/native-result.node-shim.ts +44 -0
  62. package/runtime/result/result.ts +26 -0
  63. package/runtime/result/types.ts +60 -0
  64. package/runtime/schema/schema.ts +321 -0
  65. package/runtime/schema/types.ts +152 -0
  66. package/runtime/sleep/sleep.ts +14 -0
  67. package/runtime/sleep/types.ts +44 -0
  68. package/runtime/sntp/sntp.ts +54 -0
  69. package/runtime/sntp/types.ts +38 -0
  70. package/runtime/spi/spi.ts +31 -0
  71. package/runtime/spi/types.ts +42 -0
  72. package/runtime/stdio/stdio.ts +44 -0
  73. package/runtime/stdio/types.ts +22 -0
  74. package/runtime/stream/stream.ts +150 -0
  75. package/runtime/stream/types.ts +47 -0
  76. package/runtime/sys/sys.ts +90 -0
  77. package/runtime/sys/types.ts +131 -0
  78. package/runtime/test/test.ts +595 -0
  79. package/runtime/test/types.ts +97 -0
  80. package/runtime/uart/types.ts +75 -0
  81. package/runtime/uart/uart.ts +51 -0
  82. package/runtime/wifi/types.ts +156 -0
  83. package/runtime/wifi/wifi.ts +208 -0
  84. package/scripts/bundle-runtime.js +149 -0
  85. package/scripts/compare-minifiers.js +189 -0
  86. package/scripts/compile-bytecode.sh +38 -0
  87. package/scripts/copy-prebuild.js +20 -0
  88. package/scripts/generate-symbol-map.js +146 -0
  89. package/src/builtins.cpp +82 -0
  90. package/src/cutils_compat.c +38 -0
  91. package/src/eval_bytecode.cpp +42 -0
  92. package/src/fs.cpp +878 -0
  93. package/src/mem.cpp +63 -0
  94. package/src/mik_abort.cpp +160 -0
  95. package/src/mik_app_config.cpp +358 -0
  96. package/src/mik_cbor.cpp +334 -0
  97. package/src/mik_color.cpp +46 -0
  98. package/src/mik_console.cpp +422 -0
  99. package/src/mik_inspect.cpp +850 -0
  100. package/src/mik_repl.cpp +1122 -0
  101. package/src/mik_result.cpp +344 -0
  102. package/src/mik_stdio.cpp +147 -0
  103. package/src/mik_sys.cpp +239 -0
  104. package/src/mik_text_encoding.cpp +443 -0
  105. package/src/mikrojs.cpp +942 -0
  106. package/src/modules.cpp +944 -0
  107. package/src/platform_posix.cpp +134 -0
  108. package/src/timers.cpp +208 -0
  109. package/src/utils.cpp +173 -0
@@ -0,0 +1,595 @@
1
+ /* eslint-disable no-console */
2
+ import {gc, memoryUsage, MonotonicTimestamp} from 'mikrojs/sys'
3
+ import {pendingCount as pendingHttpCount} from 'native:http'
4
+ import {activeTimers} from 'native:sys'
5
+
6
+ import type {ErrResult, OkResult, Result} from '../result/types.js'
7
+ import type {Assert, TestFn, TestOptions} from './types.js'
8
+
9
+ interface TestCase {
10
+ name: string
11
+ fn: TestFn
12
+ skip: boolean
13
+ only: boolean
14
+ todo: boolean
15
+ timeout?: number
16
+ }
17
+
18
+ interface Suite {
19
+ name: string
20
+ tests: TestCase[]
21
+ skip: boolean
22
+ only: boolean
23
+ todo: boolean
24
+ beforeAll?: TestFn
25
+ afterAll?: TestFn
26
+ beforeEach: TestFn[]
27
+ afterEach: TestFn[]
28
+ }
29
+
30
+ const suites: Suite[] = []
31
+ let currentSuite: Suite | null = null
32
+ let heapBaseline = 0
33
+
34
+ /**
35
+ * Recapture the heap baseline. Called by the harness after each suite's
36
+ * beforeAll resolves so warmup allocations (module loads, fetch/TLS
37
+ * lazy-init, wifi connection) don't count toward heapDelta. A microtask
38
+ * yield before the gc lets the beforeAll async frame's locals become
39
+ * collectible — otherwise the baseline would be inflated by vars that
40
+ * were still pinned by the suspended closure when beforeAll resolved,
41
+ * and heapAfter would come in lower than baseline (negative delta).
42
+ */
43
+ async function captureHeapBaseline(): Promise<void> {
44
+ await Promise.resolve()
45
+ gc()
46
+ heapBaseline = memoryUsage().heapUsed
47
+ }
48
+
49
+ function newSuite(name: string, flags: {skip?: boolean; only?: boolean; todo?: boolean}): Suite {
50
+ return {
51
+ name,
52
+ tests: [],
53
+ skip: flags.skip ?? false,
54
+ only: flags.only ?? false,
55
+ todo: flags.todo ?? false,
56
+ beforeEach: [],
57
+ afterEach: [],
58
+ }
59
+ }
60
+
61
+ function runSuiteFn(suite: Suite, fn: () => void): void {
62
+ suites.push(suite)
63
+ const prev = currentSuite
64
+ currentSuite = suite
65
+ fn()
66
+ currentSuite = prev
67
+ }
68
+
69
+ // --- name interpolation for .each ---
70
+
71
+ function interpolateName(template: string, value: unknown, index: number): string {
72
+ let name = template.replaceAll('%#', String(index))
73
+ name = name.replaceAll('%s', String(value))
74
+ if (typeof value === 'object' && value !== null) {
75
+ name = name.replaceAll('%o', JSON.stringify(value))
76
+ }
77
+ return name
78
+ }
79
+
80
+ // --- describe ---
81
+
82
+ type EachDescribeFn = <T>(
83
+ cases: T[],
84
+ ) => (name: string, fn: (value: T, index: number) => void) => void
85
+
86
+ function _describe(name: string, fn: () => void): void {
87
+ runSuiteFn(newSuite(name, {}), fn)
88
+ }
89
+
90
+ _describe.skip = function (name: string, fn: () => void): void {
91
+ runSuiteFn(newSuite(name, {skip: true}), fn)
92
+ }
93
+
94
+ _describe.only = function (name: string, fn: () => void): void {
95
+ runSuiteFn(newSuite(name, {only: true}), fn)
96
+ }
97
+
98
+ _describe.todo = function (name: string, fn: () => void): void {
99
+ runSuiteFn(newSuite(name, {todo: true}), fn)
100
+ }
101
+
102
+ _describe.fixme = function (name: string, fn: () => void): void {
103
+ runSuiteFn(newSuite(name, {skip: true}), fn)
104
+ }
105
+
106
+ _describe.skipIf = function (condition: unknown) {
107
+ return condition ? _describe.skip : _describe
108
+ }
109
+
110
+ _describe.runIf = function (condition: unknown) {
111
+ return condition ? _describe : _describe.skip
112
+ }
113
+
114
+ function describeEach(
115
+ variant: (name: string, fn: () => void) => void,
116
+ ): <T>(cases: T[]) => (name: string, fn: (value: T, index: number) => void) => void {
117
+ return function <T>(cases: T[]) {
118
+ return function (nameTemplate: string, fn: (value: T, index: number) => void): void {
119
+ cases.forEach((value, index) => {
120
+ variant(interpolateName(nameTemplate, value, index), () => fn(value, index))
121
+ })
122
+ }
123
+ }
124
+ }
125
+
126
+ _describe.each = describeEach(_describe) as EachDescribeFn
127
+ ;(_describe.skip as any).each = describeEach(_describe.skip) as EachDescribeFn
128
+ ;(_describe.only as any).each = describeEach(_describe.only) as EachDescribeFn
129
+ ;(_describe.todo as any).each = describeEach(_describe.todo) as EachDescribeFn
130
+ ;(_describe.fixme as any).each = describeEach(_describe.fixme) as EachDescribeFn
131
+
132
+ export const describe = _describe as typeof _describe & {
133
+ skip: typeof _describe.skip & {each: EachDescribeFn}
134
+ only: typeof _describe.only & {each: EachDescribeFn}
135
+ todo: typeof _describe.todo & {each: EachDescribeFn}
136
+ fixme: typeof _describe.fixme & {each: EachDescribeFn}
137
+ skipIf: (condition: unknown) => typeof _describe & {each: EachDescribeFn}
138
+ runIf: (condition: unknown) => typeof _describe & {each: EachDescribeFn}
139
+ each: EachDescribeFn
140
+ }
141
+
142
+ // --- test ---
143
+
144
+ type EachTestFn = <T>(
145
+ cases: T[],
146
+ ) => (name: string, fn: (value: T, index: number) => void | Promise<void>) => void
147
+
148
+ function pushTest(
149
+ where: string,
150
+ name: string,
151
+ fn: TestFn,
152
+ flags: {skip?: boolean; only?: boolean; todo?: boolean},
153
+ options?: TestOptions,
154
+ ): void {
155
+ if (!currentSuite) throw new Error(`${where}() must be inside describe()`)
156
+ currentSuite.tests.push({
157
+ name,
158
+ fn,
159
+ skip: flags.skip ?? false,
160
+ only: flags.only ?? false,
161
+ todo: flags.todo ?? false,
162
+ timeout: options?.timeout,
163
+ })
164
+ }
165
+
166
+ function _test(name: string, fn: TestFn, options?: TestOptions): void {
167
+ pushTest('test', name, fn, {}, options)
168
+ }
169
+
170
+ _test.skip = function (name: string, fn: TestFn, options?: TestOptions): void {
171
+ pushTest('test.skip', name, fn, {skip: true}, options)
172
+ }
173
+
174
+ _test.only = function (name: string, fn: TestFn, options?: TestOptions): void {
175
+ pushTest('test.only', name, fn, {only: true}, options)
176
+ }
177
+
178
+ _test.fixme = function (name: string, fn: TestFn, options?: TestOptions): void {
179
+ pushTest('test.fixme', name, fn, {skip: true}, options)
180
+ }
181
+
182
+ _test.skipIf = function (condition: unknown) {
183
+ return condition ? _test.skip : _test
184
+ }
185
+
186
+ _test.runIf = function (condition: unknown) {
187
+ return condition ? _test : _test.skip
188
+ }
189
+
190
+ _test.todo = function (name: string): void {
191
+ pushTest('test.todo', name, () => {}, {todo: true})
192
+ }
193
+
194
+ function testEach(
195
+ variant: (name: string, fn: TestFn) => void,
196
+ ): <T>(
197
+ cases: T[],
198
+ ) => (name: string, fn: (value: T, index: number) => void | Promise<void>) => void {
199
+ return function <T>(cases: T[]) {
200
+ return function (
201
+ nameTemplate: string,
202
+ fn: (value: T, index: number) => void | Promise<void>,
203
+ ): void {
204
+ cases.forEach((value, index) => {
205
+ variant(interpolateName(nameTemplate, value, index), () => fn(value, index))
206
+ })
207
+ }
208
+ }
209
+ }
210
+
211
+ _test.each = testEach(_test) as EachTestFn
212
+ ;(_test.skip as any).each = testEach(_test.skip) as EachTestFn
213
+ ;(_test.only as any).each = testEach(_test.only) as EachTestFn
214
+ ;(_test.fixme as any).each = testEach(_test.fixme) as EachTestFn
215
+
216
+ export const test = _test as typeof _test & {
217
+ skip: typeof _test.skip & {each: EachTestFn}
218
+ only: typeof _test.only & {each: EachTestFn}
219
+ fixme: typeof _test.fixme & {each: EachTestFn}
220
+ skipIf: (condition: unknown) => typeof _test & {each: EachTestFn}
221
+ runIf: (condition: unknown) => typeof _test & {each: EachTestFn}
222
+ todo: (name: string) => void
223
+ each: EachTestFn
224
+ }
225
+
226
+ // --- hooks ---
227
+
228
+ export function beforeAll(fn: TestFn): void {
229
+ if (!currentSuite) throw new Error('beforeAll() must be inside describe()')
230
+ currentSuite.beforeAll = fn
231
+ }
232
+
233
+ export function afterAll(fn: TestFn): void {
234
+ if (!currentSuite) throw new Error('afterAll() must be inside describe()')
235
+ currentSuite.afterAll = fn
236
+ }
237
+
238
+ export function beforeEach(fn: TestFn): void {
239
+ if (!currentSuite) throw new Error('beforeEach() must be inside describe()')
240
+ currentSuite.beforeEach.push(fn)
241
+ }
242
+
243
+ export function afterEach(fn: TestFn): void {
244
+ if (!currentSuite) throw new Error('afterEach() must be inside describe()')
245
+ currentSuite.afterEach.push(fn)
246
+ }
247
+
248
+ // --- assertions ---
249
+
250
+ function fmt(v: unknown): string {
251
+ if (v === undefined) return 'undefined'
252
+ if (v === null) return 'null'
253
+ if (typeof v === 'string') return JSON.stringify(v)
254
+ if (v instanceof Uint8Array) return `Uint8Array[${v.join(', ')}]`
255
+ try {
256
+ return JSON.stringify(v)
257
+ } catch {
258
+ return String(v)
259
+ }
260
+ }
261
+
262
+ class AssertError extends Error {
263
+ constructor(message: string) {
264
+ super(message)
265
+ this.name = 'AssertError'
266
+ }
267
+ }
268
+
269
+ function fail(message: string | undefined, detail: string): never {
270
+ throw new AssertError(message ? `${message}: ${detail}` : detail)
271
+ }
272
+
273
+ function assertOk<T, E>(r: Result<T, E>, message?: string): asserts r is OkResult<T> {
274
+ if (!r.ok) {
275
+ fail(message, `expected ok result, got error: ${formatThrown(r.error)}`)
276
+ }
277
+ }
278
+
279
+ function assertErr<T, E>(r: Result<T, E>, message?: string): asserts r is ErrResult<E> {
280
+ if (r.ok) {
281
+ fail(message, `expected error result, got ok: ${fmt(r.value)}`)
282
+ }
283
+ }
284
+
285
+ export const assert: Assert = {
286
+ equal(actual: unknown, expected: unknown, message?: string): void {
287
+ if (!Object.is(actual, expected)) {
288
+ fail(message, `expected ${fmt(expected)}, got ${fmt(actual)}`)
289
+ }
290
+ },
291
+
292
+ notEqual(actual: unknown, unexpected: unknown, message?: string): void {
293
+ if (Object.is(actual, unexpected)) {
294
+ fail(message, `expected value to differ from ${fmt(unexpected)}`)
295
+ }
296
+ },
297
+
298
+ truthy(value: unknown, message?: string): void {
299
+ if (!value) {
300
+ fail(message, `expected truthy, got ${fmt(value)}`)
301
+ }
302
+ },
303
+
304
+ deepEqual(actual: unknown, expected: unknown, message?: string): void {
305
+ const a = JSON.stringify(actual)
306
+ const b = JSON.stringify(expected)
307
+ if (a !== b) {
308
+ fail(message, `expected ${b}, got ${a}`)
309
+ }
310
+ },
311
+
312
+ throws(fn: () => void, message?: string): Error {
313
+ try {
314
+ fn()
315
+ } catch (e) {
316
+ return e instanceof Error ? e : new Error(String(e))
317
+ }
318
+ fail(message, 'expected function to throw')
319
+ },
320
+
321
+ async rejects(fn: () => Promise<unknown>, message?: string): Promise<Error> {
322
+ try {
323
+ await fn()
324
+ } catch (e) {
325
+ return e instanceof Error ? e : new Error(String(e))
326
+ }
327
+ fail(message, 'expected promise to reject')
328
+ },
329
+
330
+ type(value: unknown, expected: string, message?: string): void {
331
+ if (typeof value !== expected) {
332
+ fail(message, `expected typeof ${expected}, got ${typeof value}`)
333
+ }
334
+ },
335
+
336
+ instance(value: unknown, ctor: new (...args: any[]) => any, message?: string): void {
337
+ if (!(value instanceof ctor)) {
338
+ fail(message, `expected instanceof ${ctor.name}, got ${fmt(value)}`)
339
+ }
340
+ },
341
+
342
+ ok: assertOk,
343
+
344
+ err: assertErr,
345
+ }
346
+
347
+ // --- runner ---
348
+
349
+ const DEFAULT_TEST_TIMEOUT = 10_000
350
+
351
+ type TestEvent =
352
+ | {e: 1; s: string; n: number}
353
+ | {e: 2; s: string; t: string; d: number}
354
+ | {e: 3; s: string; t: string; d: number; m: string}
355
+ | {e: 4; s: string; t: string}
356
+ | {e: 5; s: string}
357
+ | {
358
+ e: 6
359
+ p: number
360
+ f: number
361
+ k: number
362
+ o: number
363
+ d: number
364
+ hb?: number
365
+ ha?: number
366
+ tb?: number
367
+ ta?: number
368
+ pb?: number
369
+ pa?: number
370
+ }
371
+ | {e: 7; s: string; m: string}
372
+ | {e: 8; u: number; t: number; f?: number; mf?: number}
373
+ | {e: 9; s: string; t: string}
374
+
375
+ const emit: (event: TestEvent) => void =
376
+ typeof (globalThis as any).__testEmit === 'function'
377
+ ? (event) => (globalThis as any).__testEmit(JSON.stringify(event))
378
+ : (event) => console.log('__TEST__' + JSON.stringify(event))
379
+
380
+ function elapsedMs(start: MonotonicTimestamp): number {
381
+ return Math.round(MonotonicTimestamp.now().since(start))
382
+ }
383
+
384
+ /** Format a thrown value for test output. Handles Error instances,
385
+ * plain objects with `name`/`message` (like our Result error shapes
386
+ * `{name: 'Network', message: '...'}`), and falls back to String(). */
387
+ function formatThrown(e: unknown): string {
388
+ if (e instanceof Error) return e.message
389
+ if (e && typeof e === 'object') {
390
+ const obj = e as {name?: unknown; message?: unknown}
391
+ if (typeof obj.message === 'string') {
392
+ return typeof obj.name === 'string' ? `${obj.name}: ${obj.message}` : obj.message
393
+ }
394
+ }
395
+ return String(e)
396
+ }
397
+
398
+ function emitHeap(): void {
399
+ gc()
400
+ const mem = memoryUsage()
401
+ const evt: TestEvent = {e: 8, u: mem.heapUsed, t: mem.heapTotal}
402
+ if (mem.systemFree > 0) evt.f = mem.systemFree
403
+ if (mem.systemMinFree > 0) evt.mf = mem.systemMinFree
404
+ emit(evt)
405
+ }
406
+
407
+ async function run(): Promise<void> {
408
+ let passed = 0
409
+ let failed = 0
410
+ let skipped = 0
411
+ let todo = 0
412
+ const startTime = MonotonicTimestamp.now()
413
+
414
+ // Leak-detection baseline: captured after gc(), with all suites already
415
+ // registered from module eval, so persistent harness allocations are
416
+ // included in the baseline rather than counted as growth. The harness
417
+ // recaptures after each suite's beforeAll resolves (see below), so
418
+ // warmup costs (module loads, TLS lazy-init, wifi connection) get
419
+ // folded into the baseline automatically.
420
+ gc()
421
+ heapBaseline = memoryUsage().heapUsed
422
+ const timersBefore = activeTimers()
423
+ const pendingBefore = pendingHttpCount()
424
+
425
+ // If any suite or test is marked .only anywhere, filter everything else.
426
+ const hasOnly = suites.some((s) => s.only || s.tests.some((t) => t.only))
427
+
428
+ for (const suite of suites) {
429
+ emitHeap()
430
+ emit({e: 1, s: suite.name, n: suite.tests.length})
431
+
432
+ if (suite.skip) {
433
+ for (const t of suite.tests) {
434
+ if (t.todo) {
435
+ emit({e: 9, s: suite.name, t: t.name})
436
+ todo++
437
+ } else {
438
+ emit({e: 4, s: suite.name, t: t.name})
439
+ skipped++
440
+ }
441
+ }
442
+ emit({e: 5, s: suite.name})
443
+ continue
444
+ }
445
+
446
+ if (suite.todo) {
447
+ for (const t of suite.tests) {
448
+ emit({e: 9, s: suite.name, t: t.name})
449
+ todo++
450
+ }
451
+ emit({e: 5, s: suite.name})
452
+ continue
453
+ }
454
+
455
+ // Only-mode: skip entire suites that are not .only and contain no .only tests.
456
+ const suiteHasOnlyTest = suite.tests.some((t) => t.only)
457
+ if (hasOnly && !suite.only && !suiteHasOnlyTest) {
458
+ for (const t of suite.tests) {
459
+ if (t.todo) {
460
+ emit({e: 9, s: suite.name, t: t.name})
461
+ todo++
462
+ } else {
463
+ emit({e: 4, s: suite.name, t: t.name})
464
+ skipped++
465
+ }
466
+ }
467
+ emit({e: 5, s: suite.name})
468
+ continue
469
+ }
470
+
471
+ // Within a participating suite, if any test is .only, skip the rest.
472
+ const onlyInSuite = hasOnly && suiteHasOnlyTest
473
+
474
+ if (suite.beforeAll) {
475
+ try {
476
+ await suite.beforeAll()
477
+ } catch (e) {
478
+ const msg = formatThrown(e)
479
+ emit({e: 7, s: suite.name, m: msg})
480
+ for (const t of suite.tests) {
481
+ if (t.todo) {
482
+ emit({e: 9, s: suite.name, t: t.name})
483
+ todo++
484
+ } else {
485
+ emit({e: 4, s: suite.name, t: t.name})
486
+ skipped++
487
+ }
488
+ }
489
+ emit({e: 5, s: suite.name})
490
+ continue
491
+ }
492
+ // Recapture the baseline now that beforeAll has fully resolved and
493
+ // its closure frame is eligible for collection. Multi-suite files
494
+ // let the last successful beforeAll set the baseline — fine because
495
+ // every earlier suite's test allocations have already been bounded
496
+ // by the previous (stricter) baseline.
497
+ await captureHeapBaseline()
498
+ }
499
+
500
+ for (const t of suite.tests) {
501
+ if (t.todo) {
502
+ emit({e: 9, s: suite.name, t: t.name})
503
+ todo++
504
+ continue
505
+ }
506
+
507
+ if (t.skip) {
508
+ emit({e: 4, s: suite.name, t: t.name})
509
+ skipped++
510
+ continue
511
+ }
512
+
513
+ if (onlyInSuite && !t.only) {
514
+ emit({e: 4, s: suite.name, t: t.name})
515
+ skipped++
516
+ continue
517
+ }
518
+
519
+ emitHeap()
520
+ const testTimeout = t.timeout ?? DEFAULT_TEST_TIMEOUT
521
+ const testStart = MonotonicTimestamp.now()
522
+ try {
523
+ await new Promise<void>((resolve, reject) => {
524
+ const timer = setTimeout(
525
+ () => reject(new Error(`timeout (${testTimeout}ms)`)),
526
+ testTimeout,
527
+ )
528
+ ;(async () => {
529
+ for (const hook of suite.beforeEach) await hook()
530
+ await t.fn()
531
+ })().then(
532
+ () => {
533
+ clearTimeout(timer)
534
+ resolve()
535
+ },
536
+ (e) => {
537
+ clearTimeout(timer)
538
+ reject(e)
539
+ },
540
+ )
541
+ })
542
+ emit({e: 2, s: suite.name, t: t.name, d: elapsedMs(testStart)})
543
+ passed++
544
+ } catch (e) {
545
+ const msg = formatThrown(e)
546
+ emit({e: 3, s: suite.name, t: t.name, d: elapsedMs(testStart), m: msg})
547
+ failed++
548
+ }
549
+ for (const hook of suite.afterEach) {
550
+ try {
551
+ await hook()
552
+ } catch {
553
+ // afterEach errors are not fatal
554
+ }
555
+ }
556
+ }
557
+
558
+ if (suite.afterAll) {
559
+ try {
560
+ await suite.afterAll()
561
+ } catch {
562
+ // afterAll errors are not fatal
563
+ }
564
+ }
565
+
566
+ emit({e: 5, s: suite.name})
567
+ }
568
+
569
+ gc()
570
+ const heapAfter = memoryUsage().heapUsed
571
+ const timersAfter = activeTimers()
572
+ const pendingAfter = pendingHttpCount()
573
+
574
+ emit({
575
+ e: 6,
576
+ p: passed,
577
+ f: failed,
578
+ k: skipped,
579
+ o: todo,
580
+ d: elapsedMs(startTime),
581
+ hb: heapBaseline,
582
+ ha: heapAfter,
583
+ tb: timersBefore,
584
+ ta: timersAfter,
585
+ pb: pendingBefore,
586
+ pa: pendingAfter,
587
+ })
588
+
589
+ // Signal the supervisor (if any) that this file is complete so it can
590
+ // swap in the next runtime. No-op in non-test-harness contexts.
591
+ ;(globalThis as any).__testFileDone?.()
592
+ }
593
+
594
+ // Auto-run after module evaluation completes
595
+ setTimeout(() => void run(), 0)
@@ -0,0 +1,97 @@
1
+ import type {ErrResult, OkResult, Result} from '../result/types.js'
2
+
3
+ export type TestFn = () => void | Promise<void>
4
+
5
+ export interface TestOptions {
6
+ timeout?: number
7
+ }
8
+
9
+ export interface Assert {
10
+ /** Strict equality (Object.is) */
11
+ equal(actual: unknown, expected: unknown, message?: string): void
12
+ /** Strict inequality */
13
+ notEqual(actual: unknown, unexpected: unknown, message?: string): void
14
+ /** Truthy check */
15
+ truthy(value: unknown, message?: string): void
16
+ /** Deep equality via JSON.stringify comparison */
17
+ deepEqual(actual: unknown, expected: unknown, message?: string): void
18
+ /** Expect function to throw, returns the error */
19
+ throws(fn: () => void, message?: string): Error
20
+ /** Expect promise to reject, returns the error */
21
+ rejects(fn: () => Promise<unknown>, message?: string): Promise<Error>
22
+ /** typeof check */
23
+ type(value: unknown, expected: string, message?: string): void
24
+ /** instanceof check */
25
+ instance(value: unknown, ctor: new (...args: any[]) => any, message?: string): void
26
+ /** Assert a Result is ok. Narrows r to OkResult; read r.value afterwards. */
27
+ ok<T, E>(r: Result<T, E>, message?: string): asserts r is OkResult<T>
28
+ /** Assert a Result is an error. Narrows r to ErrResult; read r.error afterwards. */
29
+ err<T, E>(r: Result<T, E>, message?: string): asserts r is ErrResult<E>
30
+ }
31
+
32
+ export declare const assert: Assert
33
+
34
+ export declare function describe(name: string, fn: () => void): void
35
+ // eslint-disable-next-line @typescript-eslint/no-namespace -- needed for describe.skip() syntax
36
+ export declare namespace describe {
37
+ function skip(name: string, fn: () => void): void
38
+ // eslint-disable-next-line @typescript-eslint/no-namespace -- needed for describe.skip.each() syntax
39
+ namespace skip {
40
+ function each<T>(cases: T[]): (name: string, fn: (value: T, index: number) => void) => void
41
+ }
42
+ function only(name: string, fn: () => void): void
43
+ // eslint-disable-next-line @typescript-eslint/no-namespace -- needed for describe.only.each() syntax
44
+ namespace only {
45
+ function each<T>(cases: T[]): (name: string, fn: (value: T, index: number) => void) => void
46
+ }
47
+ function todo(name: string, fn: () => void): void
48
+ // eslint-disable-next-line @typescript-eslint/no-namespace -- needed for describe.todo.each() syntax
49
+ namespace todo {
50
+ function each<T>(cases: T[]): (name: string, fn: (value: T, index: number) => void) => void
51
+ }
52
+ function fixme(name: string, fn: () => void): void
53
+ // eslint-disable-next-line @typescript-eslint/no-namespace -- needed for describe.fixme.each() syntax
54
+ namespace fixme {
55
+ function each<T>(cases: T[]): (name: string, fn: (value: T, index: number) => void) => void
56
+ }
57
+ function skipIf(condition: unknown): typeof describe
58
+ function runIf(condition: unknown): typeof describe
59
+ function each<T>(cases: T[]): (name: string, fn: (value: T, index: number) => void) => void
60
+ }
61
+
62
+ export declare function test(name: string, fn: TestFn, options?: TestOptions): void
63
+ // eslint-disable-next-line @typescript-eslint/no-namespace -- needed for test.skip() syntax
64
+ export declare namespace test {
65
+ function skip(name: string, fn: TestFn, options?: TestOptions): void
66
+ // eslint-disable-next-line @typescript-eslint/no-namespace -- needed for test.skip.each() syntax
67
+ namespace skip {
68
+ function each<T>(
69
+ cases: T[],
70
+ ): (name: string, fn: (value: T, index: number) => void | Promise<void>) => void
71
+ }
72
+ function only(name: string, fn: TestFn, options?: TestOptions): void
73
+ // eslint-disable-next-line @typescript-eslint/no-namespace -- needed for test.only.each() syntax
74
+ namespace only {
75
+ function each<T>(
76
+ cases: T[],
77
+ ): (name: string, fn: (value: T, index: number) => void | Promise<void>) => void
78
+ }
79
+ function fixme(name: string, fn: TestFn, options?: TestOptions): void
80
+ // eslint-disable-next-line @typescript-eslint/no-namespace -- needed for test.fixme.each() syntax
81
+ namespace fixme {
82
+ function each<T>(
83
+ cases: T[],
84
+ ): (name: string, fn: (value: T, index: number) => void | Promise<void>) => void
85
+ }
86
+ function skipIf(condition: unknown): typeof test
87
+ function runIf(condition: unknown): typeof test
88
+ function todo(name: string): void
89
+ function each<T>(
90
+ cases: T[],
91
+ ): (name: string, fn: (value: T, index: number) => void | Promise<void>) => void
92
+ }
93
+
94
+ export declare function beforeAll(fn: TestFn): void
95
+ export declare function afterAll(fn: TestFn): void
96
+ export declare function beforeEach(fn: TestFn): void
97
+ export declare function afterEach(fn: TestFn): void